From 077ceaed429ac0d51691e47a24305d721a971fb0 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Tue, 2 Aug 2016 19:46:58 +0200 Subject: New version of ol3 v3.17.1 --- chimere/static/ol3/ol-debug.js | 135241 +++++++++++++++++--------------------- chimere/static/ol3/ol.js | 1981 +- 2 files changed, 62262 insertions(+), 74960 deletions(-) diff --git a/chimere/static/ol3/ol-debug.js b/chimere/static/ol3/ol-debug.js index 91f7ae3..d875197 100644 --- a/chimere/static/ol3/ol-debug.js +++ b/chimere/static/ol3/ol-debug.js @@ -1,6 +1,6 @@ // OpenLayers 3. See http://openlayers.org/ // License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md -// Version: v3.16.0 +// Version: v3.17.1 (function (root, factory) { if (typeof exports === "object") { @@ -240,7 +240,7 @@ goog.define('goog.TRUSTED_SITE', true); * * This define can be used to trigger alternate implementations compatible with * running in EcmaScript Strict mode or warn about unavailable functionality. - * @see https://goo.gl/g5EoHI + * @see https://goo.gl/PudQ4y * */ goog.define('goog.STRICT_MODE_COMPATIBLE', false); @@ -600,7 +600,7 @@ goog.addDependency = function(relPath, provides, requires, opt_loadFlags) { } for (var i = 0; provide = provides[i]; i++) { deps.nameToPath[provide] = path; - deps.pathIsModule[path] = opt_loadFlags['module'] == 'goog'; + deps.loadFlags[path] = opt_loadFlags; } for (var j = 0; require = requires[j]; j++) { if (!(path in deps.requires)) { @@ -820,13 +820,32 @@ goog.loadedModules_ = {}; goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; +/** + * @define {string} How to decide whether to transpile. Valid values + * are 'always', 'never', and 'detect'. The default ('detect') is to + * use feature detection to determine which language levels need + * transpilation. + */ +// NOTE(user): we could expand this to accept a language level to bypass +// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but +// would leave ES3 and ES5 files alone. +goog.define('goog.TRANSPILE', 'detect'); + + +/** + * @define {string} Path to the transpiler. Executing the script at this + * path (relative to base.js) should define a function $jscomp.transpile. + */ +goog.define('goog.TRANSPILER', 'transpile.js'); + + if (goog.DEPENDENCIES_ENABLED) { /** * This object is used to keep track of dependencies and other data that is * used for loading scripts. * @private * @type {{ - * pathIsModule: !Object, + * loadFlags: !Object>, * nameToPath: !Object, * requires: !Object>, * visited: !Object, @@ -835,7 +854,7 @@ if (goog.DEPENDENCIES_ENABLED) { * }} */ goog.dependencies_ = { - pathIsModule: {}, // 1 to 1 + loadFlags: {}, // 1 to 1 nameToPath: {}, // 1 to 1 @@ -907,24 +926,30 @@ if (goog.DEPENDENCIES_ENABLED) { }; - /** @const @private {boolean} */ + /** + * Whether the browser is IE9 or earlier, which needs special handling + * for deferred modules. + * @const @private {boolean} + */ goog.IS_OLD_IE_ = !!(!goog.global.atob && goog.global.document && goog.global.document.all); /** - * Given a URL initiate retrieval and execution of the module. + * Given a URL initiate retrieval and execution of a script that needs + * pre-processing. * @param {string} src Script source URL. + * @param {boolean} isModule Whether this is a goog.module. + * @param {boolean} needsTranspile Whether this source needs transpilation. * @private */ - goog.importModule_ = function(src) { + goog.importProcessedScript_ = function(src, isModule, needsTranspile) { // In an attempt to keep browsers from timing out loading scripts using // synchronous XHRs, put each load in its own script block. - var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");'; + var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' + + needsTranspile + ');'; - if (goog.importScript_('', bootstrap)) { - goog.dependencies_.written[src] = true; - } + goog.importScript_('', bootstrap); }; @@ -1018,7 +1043,9 @@ if (goog.DEPENDENCIES_ENABLED) { */ goog.isDeferredModule_ = function(name) { var path = goog.getPathFromDeps_(name); - if (path && goog.dependencies_.pathIsModule[path]) { + var loadFlags = path && goog.dependencies_.loadFlags[path] || {}; + if (path && (loadFlags['module'] == 'goog' || + goog.needsTranspile_(loadFlags['lang']))) { var abspath = goog.basePath + path; return (abspath) in goog.dependencies_.deferred; } @@ -1082,69 +1109,7 @@ if (goog.DEPENDENCIES_ENABLED) { // Because this executes synchronously, we don't need to do any additional // bookkeeping. When "goog.loadModule" the namespace will be marked as // having been provided which is sufficient. - goog.retrieveAndExecModule_(url); - }; - - - /** - * @param {function(?):?|string} moduleDef The module definition. - */ - goog.loadModule = function(moduleDef) { - // NOTE: we allow function definitions to be either in the from - // of a string to eval (which keeps the original source intact) or - // in a eval forbidden environment (CSP) we allow a function definition - // which in its body must call {@code goog.module}, and return the exports - // of the module. - var previousState = goog.moduleLoaderState_; - try { - goog.moduleLoaderState_ = { - moduleName: undefined, - declareLegacyNamespace: false - }; - var exports; - if (goog.isFunction(moduleDef)) { - exports = moduleDef.call(goog.global, {}); - } else if (goog.isString(moduleDef)) { - exports = goog.loadModuleFromSource_.call(goog.global, moduleDef); - } else { - throw Error('Invalid module definition'); - } - - var moduleName = goog.moduleLoaderState_.moduleName; - if (!goog.isString(moduleName) || !moduleName) { - throw Error('Invalid module name \"' + moduleName + '\"'); - } - - // Don't seal legacy namespaces as they may be uses as a parent of - // another namespace - if (goog.moduleLoaderState_.declareLegacyNamespace) { - goog.constructNamespace_(moduleName, exports); - } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) { - Object.seal(exports); - } - - goog.loadedModules_[moduleName] = exports; - } finally { - goog.moduleLoaderState_ = previousState; - } - }; - - - /** - * @private @const {function(string):?} - * - * The new type inference warns because this function has no formal - * parameters, but its jsdoc says that it takes one argument. - * (The argument is used via arguments[0], but NTI does not detect this.) - * @suppress {newCheckTypes} - */ - goog.loadModuleFromSource_ = function() { - // NOTE: we avoid declaring parameters or local variables here to avoid - // masking globals or leaking values into the module definition. - 'use strict'; - var exports = {}; - eval(arguments[0]); - return exports; + goog.retrieveAndExec_(url, true, false); }; @@ -1227,10 +1192,8 @@ if (goog.DEPENDENCIES_ENABLED) { } } - var isOldIE = goog.IS_OLD_IE_; - if (opt_sourceText === undefined) { - if (!isOldIE) { + if (!goog.IS_OLD_IE_) { if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) { goog.appendScriptSrcNode_(src); } else { @@ -1256,6 +1219,66 @@ if (goog.DEPENDENCIES_ENABLED) { }; + /** + * Determines whether the given language needs to be transpiled. + * @param {string} lang + * @return {boolean} + * @private + */ + goog.needsTranspile_ = function(lang) { + if (goog.TRANSPILE == 'always') { + return true; + } else if (goog.TRANSPILE == 'never') { + return false; + } else if (!goog.transpiledLanguages_) { + goog.transpiledLanguages_ = {'es5': true, 'es6': true, 'es6-impl': true}; + /** @preserveTry */ + try { + // Perform some quick conformance checks, to distinguish + // between browsers that support es5, es6-impl, or es6. + + // Identify ES3-only browsers by their incorrect treatment of commas. + goog.transpiledLanguages_['es5'] = eval('[1,].length!=1'); + + // As browsers mature, features will be moved from the full test + // into the impl test. This must happen before the corresponding + // features are changed in the Closure Compiler's FeatureSet object. + + // Test 1: es6-impl [FF49, Edge 13, Chrome 49] + // (a) let/const keyword, (b) class expressions, (c) Map object, + // (d) iterable arguments, (e) spread operator + var es6implTest = + 'let a={};const X=class{constructor(){}x(z){return new Map([' + + '...arguments]).get(z[0])==3}};return new X().x([a,3])'; + + // Test 2: es6 [FF50 (?), Edge 14 (?), Chrome 50] + // (a) default params (specifically shadowing locals), + // (b) destructuring, (c) block-scoped functions, + // (d) for-of (const), (e) new.target/Reflect.construct + var es6fullTest = + 'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' + + 'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' + + 'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' + + 'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' + + '==3}'; + + if (eval('(()=>{"use strict";' + es6implTest + '})()')) { + goog.transpiledLanguages_['es6-impl'] = false; + } + if (eval('(()=>{"use strict";' + es6fullTest + '})()')) { + goog.transpiledLanguages_['es6'] = false; + } + } catch (err) { + } + } + return !!goog.transpiledLanguages_[lang]; + }; + + + /** @private {?Object} */ + goog.transpiledLanguages_ = null; + + /** @private {number} */ goog.lastNonModuleScriptIndex_ = 0; @@ -1341,10 +1364,14 @@ if (goog.DEPENDENCIES_ENABLED) { for (var i = 0; i < scripts.length; i++) { var path = scripts[i]; if (path) { - if (!deps.pathIsModule[path]) { - goog.importScript_(goog.basePath + path); + var loadFlags = deps.loadFlags[path] || {}; + var needsTranspile = goog.needsTranspile_(loadFlags['lang']); + if (loadFlags['module'] == 'goog' || needsTranspile) { + goog.importProcessedScript_( + goog.basePath + path, loadFlags['module'] == 'goog', + needsTranspile); } else { - goog.importModule_(goog.basePath + path); + goog.importScript_(goog.basePath + path); } } else { goog.moduleLoaderState_ = moduleState; @@ -1381,6 +1408,68 @@ if (goog.DEPENDENCIES_ENABLED) { } +/** + * @param {function(?):?|string} moduleDef The module definition. + */ +goog.loadModule = function(moduleDef) { + // NOTE: we allow function definitions to be either in the from + // of a string to eval (which keeps the original source intact) or + // in a eval forbidden environment (CSP) we allow a function definition + // which in its body must call {@code goog.module}, and return the exports + // of the module. + var previousState = goog.moduleLoaderState_; + try { + goog.moduleLoaderState_ = { + moduleName: undefined, + declareLegacyNamespace: false + }; + var exports; + if (goog.isFunction(moduleDef)) { + exports = moduleDef.call(undefined, {}); + } else if (goog.isString(moduleDef)) { + exports = goog.loadModuleFromSource_.call(undefined, moduleDef); + } else { + throw Error('Invalid module definition'); + } + + var moduleName = goog.moduleLoaderState_.moduleName; + if (!goog.isString(moduleName) || !moduleName) { + throw Error('Invalid module name \"' + moduleName + '\"'); + } + + // Don't seal legacy namespaces as they may be uses as a parent of + // another namespace + if (goog.moduleLoaderState_.declareLegacyNamespace) { + goog.constructNamespace_(moduleName, exports); + } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) { + Object.seal(exports); + } + + goog.loadedModules_[moduleName] = exports; + } finally { + goog.moduleLoaderState_ = previousState; + } +}; + + +/** + * @private @const {function(string):?} + * + * The new type inference warns because this function has no formal + * parameters, but its jsdoc says that it takes one argument. + * (The argument is used via arguments[0], but NTI does not detect this.) + * @suppress {newCheckTypes} + */ +goog.loadModuleFromSource_ = function() { + // NOTE: we avoid declaring parameters or local variables here to avoid + // masking globals or leaking values into the module definition. + 'use strict'; + var exports = {}; + eval(arguments[0]); + return exports; +}; + + /** * Normalize a file path by removing redundant ".." and extraneous "." file * path components. @@ -1409,28 +1498,39 @@ goog.normalizePath_ = function(path) { /** * Loads file by synchronous XHR. Should not be used in production environments. * @param {string} src Source URL. - * @return {string} File contents. + * @return {?string} File contents, or null if load failed. * @private */ goog.loadFileSync_ = function(src) { if (goog.global.CLOSURE_LOAD_FILE_SYNC) { return goog.global.CLOSURE_LOAD_FILE_SYNC(src); } else { - /** @type {XMLHttpRequest} */ - var xhr = new goog.global['XMLHttpRequest'](); - xhr.open('get', src, false); - xhr.send(); - return xhr.responseText; + try { + /** @type {XMLHttpRequest} */ + var xhr = new goog.global['XMLHttpRequest'](); + xhr.open('get', src, false); + xhr.send(); + // NOTE: Successful http: requests have a status of 200, but successful + // file: requests may have a status of zero. Any other status, or a + // thrown exception (particularly in case of file: requests) indicates + // some sort of error, which we treat as a missing or unavailable file. + return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null; + } catch (err) { + // No need to rethrow or log, since errors should show up on their own. + return null; + } } }; /** - * Retrieve and execute a module. + * Retrieve and execute a script that needs some sort of wrapping. * @param {string} src Script source URL. + * @param {boolean} isModule Whether to load as a module. + * @param {boolean} needsTranspile Whether to transpile down to ES3. * @private */ -goog.retrieveAndExecModule_ = function(src) { +goog.retrieveAndExec_ = function(src, isModule, needsTranspile) { if (!COMPILED) { // The full but non-canonicalized URL for later use. var originalPath = src; @@ -1442,23 +1542,74 @@ goog.retrieveAndExecModule_ = function(src) { goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; var scriptText = goog.loadFileSync_(src); + if (scriptText == null) { + throw new Error('Load of "' + src + '" failed'); + } - if (scriptText != null) { - var execModuleScript = goog.wrapModule_(src, scriptText); - var isOldIE = goog.IS_OLD_IE_; - if (isOldIE) { - goog.dependencies_.deferred[originalPath] = execModuleScript; - goog.queuedModules_.push(originalPath); - } else { - importScript(src, execModuleScript); - } + if (needsTranspile) { + scriptText = goog.transpile_.call(goog.global, scriptText, src); + } + + if (isModule) { + scriptText = goog.wrapModule_(src, scriptText); } else { - throw new Error('load of ' + src + 'failed'); + scriptText += '\n//# sourceURL=' + src; + } + var isOldIE = goog.IS_OLD_IE_; + if (isOldIE) { + goog.dependencies_.deferred[originalPath] = scriptText; + goog.queuedModules_.push(originalPath); + } else { + importScript(src, scriptText); } } }; +/** + * Lazily retrieves the transpiler and applies it to the source. + * @param {string} code JS code. + * @param {string} path Path to the code. + * @return {string} The transpiled code. + * @private + */ +goog.transpile_ = function(code, path) { + var jscomp = goog.global['$jscomp']; + if (!jscomp) { + goog.global['$jscomp'] = jscomp = {}; + } + var transpile = jscomp.transpile; + if (!transpile) { + var transpilerPath = goog.basePath + goog.TRANSPILER; + var transpilerCode = goog.loadFileSync_(transpilerPath); + if (transpilerCode) { + // This must be executed synchronously, since by the time we know we + // need it, we're about to load and write the ES6 code synchronously, + // so a normal script-tag load will be too slow. + eval(transpilerCode + '\n//# sourceURL=' + transpilerPath); + // Note: transpile.js reassigns goog.global['$jscomp'] so pull it again. + jscomp = goog.global['$jscomp']; + transpile = jscomp.transpile; + } + } + if (!transpile) { + // The transpiler is an optional component. If it's not available then + // replace it with a pass-through function that simply logs. + var suffix = ' requires transpilation but no transpiler was found.'; + transpile = jscomp.transpile = function(code, path) { + // TODO(user): figure out some way to get this error to show up + // in test results, noting that the failure may occur in many + // different ways, including in loadModule() before the test + // runner even comes up. + goog.logToConsole_(path + suffix); + return code; + }; + } + // Note: any transpilation errors/warnings will be logged to the console. + return transpile(code, path); +}; + + //============================================================================== // Language Enhancements //============================================================================== @@ -2464,8 +2615,11 @@ goog.defineClass.ClassDescriptor; /** - * @define {boolean} Whether the instances returned by - * goog.defineClass should be sealed when possible. + * @define {boolean} Whether the instances returned by goog.defineClass should + * be sealed when possible. + * + * When sealing is disabled the constructor function will not be wrapped by + * goog.defineClass, making it incompatible with ES6 class methods. */ goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); @@ -2481,30 +2635,46 @@ goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); * @private */ goog.defineClass.createSealingConstructor_ = function(ctr, superClass) { - if (goog.defineClass.SEAL_CLASS_INSTANCES && - Object.seal instanceof Function) { - // Don't seal subclasses of unsealable-tagged legacy classes. - if (superClass && superClass.prototype && - superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) { - return ctr; - } - /** - * @this {Object} - * @return {?} - */ - var wrappedCtr = function() { - // Don't seal an instance of a subclass when it calls the constructor of - // its super class as there is most likely still setup to do. - var instance = ctr.apply(this, arguments) || this; - instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_]; - if (this.constructor === wrappedCtr) { - Object.seal(instance); - } - return instance; - }; - return wrappedCtr; + if (!goog.defineClass.SEAL_CLASS_INSTANCES) { + // Do now wrap the constructor when sealing is disabled. Angular code + // depends on this for injection to work properly. + return ctr; } - return ctr; + + // Compute whether the constructor is sealable at definition time, rather + // than when the instance is being constructed. + var superclassSealable = !goog.defineClass.isUnsealable_(superClass); + + /** + * @this {Object} + * @return {?} + */ + var wrappedCtr = function() { + // Don't seal an instance of a subclass when it calls the constructor of + // its super class as there is most likely still setup to do. + var instance = ctr.apply(this, arguments) || this; + instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_]; + + if (this.constructor === wrappedCtr && superclassSealable && + Object.seal instanceof Function) { + Object.seal(instance); + } + return instance; + }; + + return wrappedCtr; +}; + + +/** + * @param {Function} ctr The constructor to test. + * @returns {boolean} Whether the constructor has been tagged as unsealable + * using goog.tagUnsealableClass. + * @private + */ +goog.defineClass.isUnsealable_ = function(ctr) { + return ctr && ctr.prototype && + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]; }; @@ -2553,7 +2723,7 @@ goog.defineClass.applyProperties_ = function(target, source) { /** * Sealing classes breaks the older idiom of assigning properties on the - * prototype rather than in the constructor. As such, goog.defineClass + * prototype rather than in the constructor. As such, goog.defineClass * must not seal subclasses of these old-style classes until they are fixed. * Until then, this marks a class as "broken", instructing defineClass * not to seal subclasses. @@ -2835,9 +3005,10 @@ ol.WEBGL_EXTENSIONS; // value is set in `ol.has` * @function * @api */ -ol.inherits = - goog.inherits; -// note that the newline above is necessary to satisfy the linter +ol.inherits = function(childCtor, parentCtor) { + childCtor.prototype = Object.create(parentCtor.prototype); + childCtor.prototype.constructor = childCtor; +}; /** @@ -4761,7 +4932,7 @@ goog.asserts.assert = function(condition, opt_message, var_args) { * switch(type) { * case FOO: doSomething(); break; * case BAR: doSomethingElse(); break; - * default: goog.assert.fail('Unrecognized type: ' + type); + * default: goog.asserts.fail('Unrecognized type: ' + type); * // We have only 2 types - "default:" section is unreachable code. * } * @@ -5308,757 +5479,11 @@ ol.object.isEmpty = function(object) { return !property; }; -/** - * File for all typedefs used by the compiler, and referenced by JSDoc. - * - * These look like vars (or var properties), but in fact are simply identifiers - * for the Closure compiler. Originally they were included in the appropriate - * namespace file, but with the move away from Closure namespaces and towards - * self-contained standard modules are now all in this file. - * Unlike the other type definitions - enums and constructor functions - they - * are not code and so are not imported or exported. They are only referred to - * in type-defining comments used by the Closure compiler, and so should not - * appear in module code. - * - * When the code is converted to ES6 modules, the namespace structure will - * disappear, and these typedefs will have to be renamed accordingly, but the - * namespace structure is maintained for the present for backwards compatibility. - * - * In principle, typedefs should not have a `goog.provide` nor should files which - * refer to a typedef in comments need a `goog.require`. However, goog.provides - * are needed for 2 cases, both to prevent compiler errors/warnings: - * - the 1st two for specific errors - * - each sub-namespace needs at least one so the namespace is created when not - * used in the code, as when application code is compiled with the library. - */ -goog.provide('ol.Extent'); -goog.provide('ol.events.EventTargetLike'); - -goog.provide('ol.interaction.DragBoxEndConditionType'); -goog.provide('ol.proj.ProjectionLike'); -goog.provide('ol.raster.Operation'); -goog.provide('ol.style.AtlasBlock'); - - -/** - * @typedef {string|Array.|ol.Attribution|Array.} - * @api - */ -ol.AttributionLike; - - -/** - * @typedef {{fillStyle: ol.ColorLike}} - */ -ol.CanvasFillState; - - -/** - * A function returning the canvas element (`{HTMLCanvasElement}`) - * used by the source as an image. The arguments passed to the function are: - * {@link ol.Extent} the image extent, `{number}` the image resolution, - * `{number}` the device pixel ratio, {@link ol.Size} the image size, and - * {@link ol.proj.Projection} the image projection. The canvas returned by - * this function is cached by the source. The this keyword inside the function - * references the {@link ol.source.ImageCanvas}. - * - * @typedef {function(this:ol.source.ImageCanvas, ol.Extent, number, - * number, ol.Size, ol.proj.Projection): HTMLCanvasElement} - * @api - */ -ol.CanvasFunctionType; - - -/** - * @typedef {{lineCap: string, - * lineDash: Array., - * lineJoin: string, - * lineWidth: number, - * miterLimit: number, - * strokeStyle: string}} - */ -ol.CanvasStrokeState; - - -/** - * @typedef {{font: string, - * textAlign: string, - * textBaseline: string}} - */ -ol.CanvasTextState; - - -/** - * @typedef {function((ol.Coordinate|undefined)): (ol.Coordinate|undefined)} - */ -ol.CenterConstraintType; - - -/** - * A color represented as a short array [red, green, blue, alpha]. - * red, green, and blue should be integers in the range 0..255 inclusive. - * alpha should be a float in the range 0..1 inclusive. If no alpha value is - * given then `1` will be used. - * @typedef {Array.} - * @api - */ -ol.Color; - - -/** - * A type accepted by CanvasRenderingContext2D.fillStyle. - * Represents a color, pattern, or gradient. - * - * @typedef {string|CanvasPattern|CanvasGradient} - * @api - */ -ol.ColorLike; - - -/** - * An array of numbers representing an xy coordinate. Example: `[16, 48]`. - * @typedef {Array.} ol.Coordinate - * @api stable - */ -ol.Coordinate; - - -/** - * A function that takes a {@link ol.Coordinate} and transforms it into a - * `{string}`. - * - * @typedef {function((ol.Coordinate|undefined)): string} - * @api stable - */ -ol.CoordinateFormatType; - - -/** - * An array of numbers representing an extent: `[minx, miny, maxx, maxy]`. - * @typedef {Array.} - * @api stable - */ -ol.Extent; - - -/** - * {@link ol.source.Vector} sources use a function of this type to load - * features. - * - * This function takes an {@link ol.Extent} representing the area to be loaded, - * a `{number}` representing the resolution (map units per pixel) and an - * {@link ol.proj.Projection} for the projection as arguments. `this` within - * the function is bound to the {@link ol.source.Vector} it's called from. - * - * The function is responsible for loading the features and adding them to the - * source. - * @api - * @typedef {function(this:ol.source.Vector, ol.Extent, number, - * ol.proj.Projection)} - */ -ol.FeatureLoader; - - -/** - * A function that returns an array of {@link ol.style.Style styles} given a - * resolution. The `this` keyword inside the function references the - * {@link ol.Feature} to be styled. - * - * @typedef {function(this: ol.Feature, number): - * (ol.style.Style|Array.)} - * @api stable - */ -ol.FeatureStyleFunction; - - -/** - * {@link ol.source.Vector} sources use a function of this type to get the url - * to load features from. - * - * This function takes an {@link ol.Extent} representing the area to be loaded, - * a `{number}` representing the resolution (map units per pixel) and an - * {@link ol.proj.Projection} for the projection as arguments and returns a - * `{string}` representing the URL. - * @api - * @typedef {function(ol.Extent, number, ol.proj.Projection) : string} - */ -ol.FeatureUrlFunction; - - -/** - * A function that is called to trigger asynchronous canvas drawing. It is - * called with a "done" callback that should be called when drawing is done. - * If any error occurs during drawing, the "done" callback should be called with - * that error. - * - * @typedef {function(function(Error))} - */ -ol.ImageCanvasLoader; - - -/** - * A function that takes an {@link ol.Image} for the image and a `{string}` for - * the src as arguments. It is supposed to make it so the underlying image - * {@link ol.Image#getImage} is assigned the content specified by the src. If - * not specified, the default is - * - * function(image, src) { - * image.getImage().src = src; - * } - * - * Providing a custom `imageLoadFunction` can be useful to load images with - * post requests or - in general - through XHR requests, where the src of the - * image element would be set to a data URI when the content is loaded. - * - * @typedef {function(ol.Image, string)} - * @api - */ -ol.ImageLoadFunctionType; - - -/** - * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined), - * y: number, yunits: (ol.style.IconAnchorUnits|undefined)}} - */ -ol.KMLVec2_; - - -/** - * @typedef {{flatCoordinates: Array., - * whens: Array.}} - */ -ol.KMLGxTrackObject_; - - -/** - * @typedef {{layer: ol.layer.Layer, - * opacity: number, - * sourceState: ol.source.State, - * visible: boolean, - * managed: boolean, - * extent: (ol.Extent|undefined), - * zIndex: number, - * maxResolution: number, - * minResolution: number}} - */ -ol.LayerState; - - -/** - * One of `all`, `bbox`, `tile`. - * - * @typedef {function(ol.Extent, number): Array.} - * @api - */ -ol.LoadingStrategy; - - -/** - * @typedef {{key_: string, - * newer: ol.LRUCacheEntry, - * older: ol.LRUCacheEntry, - * value_: *}} - */ -ol.LRUCacheEntry; - - -/** - * @typedef {{controls: ol.Collection., - * interactions: ol.Collection., - * keyboardEventTarget: (Element|Document), - * logos: (Object.), - * overlays: ol.Collection., - * rendererConstructor: - * function(new: ol.renderer.Map, Element, ol.Map), - * values: Object.}} - */ -ol.MapOptionsInternal; - - -/** - * An array with two elements, representing a pixel. The first element is the - * x-coordinate, the second the y-coordinate of the pixel. - * @typedef {Array.} - * @api stable - */ -ol.Pixel; - - -/** - * @typedef {function(ol.Map, ?olx.FrameState): boolean} - */ -ol.PostRenderFunction; - - -/** - * Function to perform manipulations before rendering. This function is called - * with the {@link ol.Map} as first and an optional {@link olx.FrameState} as - * second argument. Return `true` to keep this function for the next frame, - * `false` to remove it. - * @typedef {function(ol.Map, ?olx.FrameState): boolean} - * @api - */ -ol.PreRenderFunction; - - -/** - * @typedef {function(ol.Extent, number, number) : ol.ImageBase} - */ -ol.ReprojImageFunctionType; - - -/** - * @typedef {function(number, number, number, number) : ol.Tile} - */ -ol.ReprojTileFunctionType; - - -/** - * Single triangle; consists of 3 source points and 3 target points. - * - * @typedef {{source: Array., - * target: Array.}} - */ -ol.ReprojTriangle; - - -/** - * @typedef {function((number|undefined), number, number): (number|undefined)} - */ -ol.ResolutionConstraintType; - - -/** - * @typedef {function((number|undefined), number): (number|undefined)} - */ -ol.RotationConstraintType; - - -/** - * An array of numbers representing a size: `[width, height]`. - * @typedef {Array.} - * @api stable - */ -ol.Size; - - -/** - * @typedef {{attributions: (ol.AttributionLike|undefined), - * extent: (null|ol.Extent|undefined), - * logo: (string|olx.LogoOptions|undefined), - * projection: ol.proj.ProjectionLike, - * resolutions: (Array.|undefined), - * state: (ol.source.State|undefined)}} - */ -ol.SourceImageOptions; - - -/** - * @typedef {{revision: number, - * resolution: number, - * extent: ol.Extent}} - */ -ol.SourceRasterRenderedState; - - -/** - * @typedef {{attributions: (ol.AttributionLike|undefined), - * logo: (string|olx.LogoOptions|undefined), - * projection: ol.proj.ProjectionLike, - * state: (ol.source.State|undefined), - * wrapX: (boolean|undefined)}} - */ -ol.SourceSourceOptions; - - -/** - * @typedef {{attributions: (ol.AttributionLike|undefined), - * cacheSize: (number|undefined), - * extent: (ol.Extent|undefined), - * logo: (string|olx.LogoOptions|undefined), - * opaque: (boolean|undefined), - * tilePixelRatio: (number|undefined), - * projection: ol.proj.ProjectionLike, - * state: (ol.source.State|undefined), - * tileGrid: (ol.tilegrid.TileGrid|undefined), - * wrapX: (boolean|undefined)}} - */ -ol.SourceTileOptions; - - -/** - * @typedef {{attributions: (ol.AttributionLike|undefined), - * cacheSize: (number|undefined), - * extent: (ol.Extent|undefined), - * logo: (string|olx.LogoOptions|undefined), - * opaque: (boolean|undefined), - * projection: ol.proj.ProjectionLike, - * state: (ol.source.State|undefined), - * tileGrid: (ol.tilegrid.TileGrid|undefined), - * tileLoadFunction: ol.TileLoadFunctionType, - * tilePixelRatio: (number|undefined), - * tileUrlFunction: (ol.TileUrlFunctionType|undefined), - * url: (string|undefined), - * urls: (Array.|undefined), - * wrapX: (boolean|undefined)}} - */ -ol.SourceUrlTileOptions; - - -/** - * An array of three numbers representing the location of a tile in a tile - * grid. The order is `z`, `x`, and `y`. `z` is the zoom level. - * @typedef {Array.} ol.TileCoord - * @api - */ -ol.TileCoord; - - -/** - * A function that takes an {@link ol.Tile} for the tile and a `{string}` for - * the url as arguments. - * - * @typedef {function(ol.Tile, string)} - * @api - */ -ol.TileLoadFunctionType; - - -/** - * @typedef {function(ol.Tile, string, ol.Coordinate, number): number} - */ -ol.TilePriorityFunction; - - -/** - * @typedef {{ - * dirty: boolean, - * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number), - * renderedTileRevision: number, - * renderedRevision: number, - * replayGroup: ol.render.IReplayGroup, - * skippedFeatures: Array.}} - */ -ol.TileReplayState; - - -/** - * {@link ol.source.Tile} sources use a function of this type to get the url - * that provides a tile for a given tile coordinate. - * - * This function takes an {@link ol.TileCoord} for the tile coordinate, a - * `{number}` representing the pixel ratio and an {@link ol.proj.Projection} for - * the projection as arguments and returns a `{string}` representing the tile - * URL, or undefined if no tile should be requested for the passed tile - * coordinate. - * - * @typedef {function(ol.TileCoord, number, - * ol.proj.Projection): (string|undefined)} - * @api - */ -ol.TileUrlFunctionType; - - -/** - * A transform function accepts an array of input coordinate values, an optional - * output array, and an optional dimension (default should be 2). The function - * transforms the input coordinate values, populates the output array, and - * returns the output array. - * - * @typedef {function(Array., Array.=, number=): Array.} - * @api stable - */ -ol.TransformFunction; - - -/** - * @typedef {{buf: ol.webgl.Buffer, - * buffer: WebGLBuffer}} - */ -ol.WebglBufferCacheEntry; - - -/** - * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}} - */ -ol.WebglTextureCacheEntry; - - -/** - * Number of features; bounds/extent. - * @typedef {{numberOfFeatures: number, - * bounds: ol.Extent}} - * @api stable - */ -ol.WFSFeatureCollectionMetadata; - - -/** - * Total deleted; total inserted; total updated; array of insert ids. - * @typedef {{totalDeleted: number, - * totalInserted: number, - * totalUpdated: number, - * insertIds: Array.}} - * @api stable - */ -ol.WFSTransactionResponse; - - -/** - * @typedef {{type: number, value: (number|string|undefined), position: number}} - */ -ol.WKTToken; - - -/** - * When using {@link ol.xml.makeChildAppender} or - * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to - * have this structure. - * @typedef {{node:Node}} - */ -ol.XmlNodeStackItem; - - -/** - * @typedef {function(Node, Array.<*>)} - */ -ol.XmlParser; - - -/** - * @typedef {function(Node, *, Array.<*>)} - */ -ol.XmlSerializer; - - -/** - * A function that takes an {@link ol.MapBrowserEvent} and returns a - * `{boolean}`. If the condition is met, true should be returned. - * - * @typedef {function(ol.MapBrowserEvent): boolean} - * @api stable - */ -ol.events.ConditionType; - - -/** - * @typedef {EventTarget|ol.events.EventTarget| - * {addEventListener: function(string, Function, boolean=), - * removeEventListener: function(string, Function, boolean=), - * dispatchEvent: function(string)}} - */ -ol.events.EventTargetLike; - - -/** - * Key to use with {@link ol.Observable#unByKey}. - * - * @typedef {{bindTo: (Object|undefined), - * boundListener: (ol.events.ListenerFunctionType|undefined), - * callOnce: boolean, - * deleteIndex: (number|undefined), - * listener: ol.events.ListenerFunctionType, - * target: (EventTarget|ol.events.EventTarget), - * type: string}} - * @api - */ -ol.events.Key; - - -/** - * Listener function. This function is called with an event object as argument. - * When the function returns `false`, event propagation will stop. - * - * @typedef {function(ol.events.Event)|function(ol.events.Event): boolean} - * @api - */ -ol.events.ListenerFunctionType; - - -/** - * A function that takes a {@link ol.MapBrowserEvent} and two - * {@link ol.Pixel}s and returns a `{boolean}`. If the condition is met, - * true should be returned. - * @typedef {function(ol.MapBrowserEvent, ol.Pixel, ol.Pixel):boolean} - * @api - */ -ol.interaction.DragBoxEndConditionType; - - -/** - * Function that takes coordinates and an optional existing geometry as - * arguments, and returns a geometry. The optional existing geometry is the - * geometry that is returned when the function is called without a second - * argument. - * @typedef {function(!(ol.Coordinate|Array.| - * Array.>), ol.geom.SimpleGeometry=): - * ol.geom.SimpleGeometry} - * @api - */ -ol.interaction.DrawGeometryFunctionType; - - -/** - * @typedef {{depth: (Array.|undefined), - * feature: ol.Feature, - * geometry: ol.geom.SimpleGeometry, - * index: (number|undefined), - * segment: Array.}} - */ -ol.interaction.SegmentDataType; - - -/** - * A function that takes an {@link ol.Feature} or {@link ol.render.Feature} and - * an {@link ol.layer.Layer} and returns `true` if the feature may be selected - * or `false` otherwise. - * @typedef {function((ol.Feature|ol.render.Feature), ol.layer.Layer): - * boolean} - * @api - */ -ol.interaction.SelectFilterFunction; - - -/** - * @typedef {{ - * snapped: {boolean}, - * vertex: (ol.Coordinate|null), - * vertexPixel: (ol.Pixel|null) - * }} - */ -ol.interaction.SnapResultType; - - -/** - * @typedef {{ - * feature: ol.Feature, - * segment: Array. - * }} - */ -ol.interaction.SnapSegmentDataType; - - -/** - * A projection as {@link ol.proj.Projection}, SRS identifier string or - * undefined. - * @typedef {ol.proj.Projection|string|undefined} ol.proj.ProjectionLike - * @api stable - */ -ol.proj.ProjectionLike; - - -/** - * A function that takes an array of input data, performs some operation, and - * returns an array of ouput data. For `'pixel'` type operations, functions - * will be called with an array of {@link ol.raster.Pixel} data and should - * return an array of the same. For `'image'` type operations, functions will - * be called with an array of {@link ImageData - * https://developer.mozilla.org/en-US/docs/Web/API/ImageData} and should return - * an array of the same. The operations are called with a second "data" - * argument, which can be used for storage. The data object is accessible - * from raster events, where it can be initialized in "beforeoperations" and - * accessed again in "afteroperations". - * - * @typedef {function((Array.|Array.), Object): - * (Array.|Array.)} - * @api - */ -ol.raster.Operation; - - -/** - * An array of numbers representing pixel values. - * @typedef {Array.} ol.raster.Pixel - * @api - */ -ol.raster.Pixel; - - -/** - * @typedef {{x: number, y: number, width: number, height: number}} - */ -ol.style.AtlasBlock; - - -/** - * Provides information for an image inside an atlas. - * `offsetX` and `offsetY` are the position of the image inside - * the atlas image `image`. - * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}} - */ -ol.style.AtlasInfo; - - -/** - * Provides information for an image inside an atlas manager. - * `offsetX` and `offsetY` is the position of the image inside - * the atlas image `image` and the position of the hit-detection image - * inside the hit-detection atlas image `hitImage`. - * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement, - * hitImage: HTMLCanvasElement}} - */ -ol.style.AtlasManagerInfo; - - -/** - * @typedef {{strokeStyle: (string|undefined), strokeWidth: number, - * size: number, lineDash: Array.}} - */ -ol.style.CircleRenderOptions; - - -/** - * @typedef {{opacity: number, - * rotateWithView: boolean, - * rotation: number, - * scale: number, - * snapToPixel: boolean}} - */ -ol.style.ImageOptions; - - -/** - * A function that takes an {@link ol.Feature} as argument and returns an - * {@link ol.geom.Geometry} that will be rendered and styled for the feature. - * - * @typedef {function((ol.Feature|ol.render.Feature)): - * (ol.geom.Geometry|ol.render.Feature|undefined)} - * @api - */ -ol.style.GeometryFunction; - - -/** - * @typedef {{ - * strokeStyle: (string|undefined), - * strokeWidth: number, - * size: number, - * lineCap: string, - * lineDash: Array., - * lineJoin: string, - * miterLimit: number - * }} - */ -ol.style.RegularShapeRenderOptions; - - -/** - * A function that takes an {@link ol.Feature} and a `{number}` representing - * the view's resolution. The function should return a {@link ol.style.Style} - * or an array of them. This way e.g. a vector layer can be styled. - * - * @typedef {function((ol.Feature|ol.render.Feature), number): - * (ol.style.Style|Array.)} - * @api - */ -ol.style.StyleFunction; - goog.provide('ol.events'); goog.provide('ol.events.EventType'); goog.provide('ol.events.KeyCode'); goog.require('ol.object'); -goog.require('ol.events.EventTargetLike'); /** @@ -6118,8 +5543,8 @@ ol.events.LISTENER_MAP_PROP_ = 'olm_' + ((Math.random() * 1e4) | 0); /** - * @param {ol.events.Key} listenerObj Listener object. - * @return {ol.events.ListenerFunctionType} Bound listener. + * @param {ol.EventsKey} listenerObj Listener object. + * @return {ol.EventsListenerFunctionType} Bound listener. */ ol.events.bindListener_ = function(listenerObj) { var boundListener = function(evt) { @@ -6129,22 +5554,22 @@ ol.events.bindListener_ = function(listenerObj) { ol.events.unlistenByKey(listenerObj); } return listener.call(bindTo, evt); - } + }; listenerObj.boundListener = boundListener; return boundListener; }; /** - * Finds the matching {@link ol.events.Key} in the given listener + * Finds the matching {@link ol.EventsKey} in the given listener * array. * - * @param {!Array} listeners Array of listeners. + * @param {!Array} listeners Array of listeners. * @param {!Function} listener The listener function. * @param {Object=} opt_this The `this` value inside the listener. * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching * listener, for {@link ol.events.unlistenByKey}. - * @return {ol.events.Key|undefined} The matching listener object. + * @return {ol.EventsKey|undefined} The matching listener object. * @private */ ol.events.findListener_ = function(listeners, listener, opt_this, @@ -6165,9 +5590,9 @@ ol.events.findListener_ = function(listeners, listener, opt_this, /** - * @param {ol.events.EventTargetLike} target Target. + * @param {ol.EventTargetLike} target Target. * @param {string} type Type. - * @return {Array.|undefined} Listeners. + * @return {Array.|undefined} Listeners. */ ol.events.getListeners = function(target, type) { var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; @@ -6178,8 +5603,8 @@ ol.events.getListeners = function(target, type) { /** * Get the lookup of listeners. If one does not exist on the target, it is * created. - * @param {ol.events.EventTargetLike} target Target. - * @return {!Object.>} Map of + * @param {ol.EventTargetLike} target Target. + * @return {!Object.>} Map of * listeners by event type. * @private */ @@ -6196,7 +5621,7 @@ ol.events.getListenerMap_ = function(target) { * Clean up all listener objects of the given type. All properties on the * listener objects will be removed, and if no listeners remain in the listener * map, it will be removed from the target. - * @param {ol.events.EventTargetLike} target Target. + * @param {ol.EventTargetLike} target Target. * @param {string} type Type. * @private */ @@ -6205,7 +5630,7 @@ ol.events.removeListeners_ = function(target, type) { if (listeners) { for (var i = 0, ii = listeners.length; i < ii; ++i) { target.removeEventListener(type, listeners[i].boundListener); - ol.object.clear(listeners[i]) + ol.object.clear(listeners[i]); } listeners.length = 0; var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; @@ -6226,13 +5651,13 @@ ol.events.removeListeners_ = function(target, type) { * This function efficiently binds a `listener` to a `this` object, and returns * a key for use with {@link ol.events.unlistenByKey}. * - * @param {ol.events.EventTargetLike} target Event target. + * @param {ol.EventTargetLike} target Event target. * @param {string} type Event type. - * @param {ol.events.ListenerFunctionType} listener Listener. + * @param {ol.EventsListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the * listener. Default is the `target`. * @param {boolean=} opt_once If true, add the listener as one-off listener. - * @return {ol.events.Key} Unique key for the listener. + * @return {ol.EventsKey} Unique key for the listener. */ ol.events.listen = function(target, type, listener, opt_this, opt_once) { var listenerMap = ol.events.getListenerMap_(target); @@ -6248,7 +5673,7 @@ ol.events.listen = function(target, type, listener, opt_this, opt_once) { listenerObj.callOnce = false; } } else { - listenerObj = /** @type {ol.events.Key} */ ({ + listenerObj = /** @type {ol.EventsKey} */ ({ bindTo: opt_this, callOnce: !!opt_once, listener: listener, @@ -6276,12 +5701,12 @@ ol.events.listen = function(target, type, listener, opt_this, opt_once) { * function, the self-unregistering listener will be turned into a permanent * listener. * - * @param {ol.events.EventTargetLike} target Event target. + * @param {ol.EventTargetLike} target Event target. * @param {string} type Event type. - * @param {ol.events.ListenerFunctionType} listener Listener. + * @param {ol.EventsListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the * listener. Default is the `target`. - * @return {ol.events.Key} Key for unlistenByKey. + * @return {ol.EventsKey} Key for unlistenByKey. */ ol.events.listenOnce = function(target, type, listener, opt_this) { return ol.events.listen(target, type, listener, opt_this, true); @@ -6295,9 +5720,9 @@ ol.events.listenOnce = function(target, type, listener, opt_this) { * To return a listener, this function needs to be called with the exact same * arguments that were used for a previous {@link ol.events.listen} call. * - * @param {ol.events.EventTargetLike} target Event target. + * @param {ol.EventTargetLike} target Event target. * @param {string} type Event type. - * @param {ol.events.ListenerFunctionType} listener Listener. + * @param {ol.EventsListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the * listener. Default is the `target`. */ @@ -6320,7 +5745,7 @@ ol.events.unlisten = function(target, type, listener, opt_this) { * The argument passed to this function is the key returned from * {@link ol.events.listen} or {@link ol.events.listenOnce}. * - * @param {ol.events.Key} key The key. + * @param {ol.EventsKey} key The key. */ ol.events.unlistenByKey = function(key) { if (key && key.target) { @@ -6344,7 +5769,7 @@ ol.events.unlistenByKey = function(key) { * Unregisters all event listeners on an event target. Inspired by * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} * - * @param {ol.events.EventTargetLike} target Target. + * @param {ol.EventTargetLike} target Target. */ ol.events.unlistenAll = function(target) { var listenerMap = ol.events.getListenerMap_(target); @@ -6467,6 +5892,7 @@ goog.require('ol.Disposable'); goog.require('ol.events'); goog.require('ol.events.Event'); + /** * @classdesc * A simplified implementation of the W3C DOM Level 2 EventTarget interface. @@ -6487,7 +5913,7 @@ goog.require('ol.events.Event'); */ ol.events.EventTarget = function() { - goog.base(this); + ol.Disposable.call(this); /** * @private @@ -6503,17 +5929,17 @@ ol.events.EventTarget = function() { /** * @private - * @type {!Object.>} + * @type {!Object.>} */ this.listeners_ = {}; }; -goog.inherits(ol.events.EventTarget, ol.Disposable); +ol.inherits(ol.events.EventTarget, ol.Disposable); /** * @param {string} type Type. - * @param {ol.events.ListenerFunctionType} listener Listener. + * @param {ol.EventsListenerFunctionType} listener Listener. */ ol.events.EventTarget.prototype.addEventListener = function(type, listener) { var listeners = this.listeners_[type]; @@ -6578,7 +6004,7 @@ ol.events.EventTarget.prototype.disposeInternal = function() { * order that they will be called in. * * @param {string} type Type. - * @return {Array.} Listeners. + * @return {Array.} Listeners. */ ol.events.EventTarget.prototype.getListeners = function(type) { return this.listeners_[type]; @@ -6599,7 +6025,7 @@ ol.events.EventTarget.prototype.hasListener = function(opt_type) { /** * @param {string} type Type. - * @param {ol.events.ListenerFunctionType} listener Listener. + * @param {ol.EventsListenerFunctionType} listener Listener. */ ol.events.EventTarget.prototype.removeEventListener = function(type, listener) { var listeners = this.listeners_[type]; @@ -6642,7 +6068,7 @@ goog.require('ol.events.EventType'); */ ol.Observable = function() { - goog.base(this); + ol.events.EventTarget.call(this); /** * @private @@ -6651,12 +6077,12 @@ ol.Observable = function() { this.revision_ = 0; }; -goog.inherits(ol.Observable, ol.events.EventTarget); +ol.inherits(ol.Observable, ol.events.EventTarget); /** * Removes an event listener using the key returned by `on()` or `once()`. - * @param {ol.events.Key|Array.} key The key returned by `on()` + * @param {ol.EventsKey|Array.} key The key returned by `on()` * or `once()` (or an array of keys). * @api stable */ @@ -6666,7 +6092,7 @@ ol.Observable.unByKey = function(key) { ol.events.unlistenByKey(key[i]); } } else { - ol.events.unlistenByKey(/** @type {ol.events.Key} */ (key)); + ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key)); } }; @@ -6718,7 +6144,7 @@ ol.Observable.prototype.getRevision = function() { * @param {string|Array.} type The event type or array of event types. * @param {function(?): ?} listener The listener function. * @param {Object=} opt_this The object to use as `this` in `listener`. - * @return {ol.events.Key|Array.} Unique key for the listener. If + * @return {ol.EventsKey|Array.} Unique key for the listener. If * called with an array of event types as the first argument, the return * will be an array of keys. * @api stable @@ -6743,7 +6169,7 @@ ol.Observable.prototype.on = function(type, listener, opt_this) { * @param {string|Array.} type The event type or array of event types. * @param {function(?): ?} listener The listener function. * @param {Object=} opt_this The object to use as `this` in `listener`. - * @return {ol.events.Key|Array.} Unique key for the listener. If + * @return {ol.EventsKey|Array.} Unique key for the listener. If * called with an array of event types as the first argument, the return * will be an array of keys. * @api stable @@ -6787,7 +6213,7 @@ ol.Observable.prototype.un = function(type, listener, opt_this) { * Removes an event listener using the key returned by `on()` or `once()`. * Note that using the {@link ol.Observable.unByKey} static function is to * be preferred. - * @param {ol.events.Key|Array.} key The key returned by `on()` + * @param {ol.EventsKey|Array.} key The key returned by `on()` * or `once()` (or an array of keys). * @function * @api stable @@ -6829,7 +6255,7 @@ ol.ObjectEventType = { * @constructor */ ol.ObjectEvent = function(type, key, oldValue) { - goog.base(this, type); + ol.events.Event.call(this, type); /** * The name of the property whose value is changing. @@ -6847,7 +6273,7 @@ ol.ObjectEvent = function(type, key, oldValue) { this.oldValue = oldValue; }; -goog.inherits(ol.ObjectEvent, ol.events.Event); +ol.inherits(ol.ObjectEvent, ol.events.Event); /** @@ -6896,7 +6322,7 @@ goog.inherits(ol.ObjectEvent, ol.events.Event); * @api */ ol.Object = function(opt_values) { - goog.base(this); + ol.Observable.call(this); // Call goog.getUid to ensure that the order of objects' ids is the same as // the order in which they were created. This also helps to ensure that @@ -6914,7 +6340,7 @@ ol.Object = function(opt_values) { this.setProperties(opt_values); } }; -goog.inherits(ol.Object, ol.Observable); +ol.inherits(ol.Object, ol.Observable); /** @@ -7072,7 +6498,7 @@ ol.array.binarySearch = function(haystack, needle, opt_comparator) { /* Key not found. */ return found ? low : ~low; -} +}; /** * @param {Array.} arr Array. @@ -7224,11 +6650,11 @@ ol.array.flatten = function(arr) { ol.array.extend = function(arr, data) { var i; var extension = goog.isArrayLike(data) ? data : [data]; - var length = extension.length + var length = extension.length; for (i = 0; i < length; i++) { arr[arr.length] = extension[i]; } -} +}; /** @@ -7244,7 +6670,7 @@ ol.array.remove = function(arr, obj) { arr.splice(i, 1); } return found; -} +}; /** @@ -7264,7 +6690,7 @@ ol.array.find = function(arr, func) { } } return null; -} +}; /** @@ -7283,7 +6709,7 @@ ol.array.equals = function(arr1, arr2) { } } return true; -} +}; /** @@ -7303,7 +6729,7 @@ ol.array.stableSort = function(arr, compareFnc) { for (i = 0; i < arr.length; i++) { arr[i] = tmp[i].value; } -} +}; /** @@ -7318,7 +6744,7 @@ ol.array.findIndex = function(arr, func) { return !func(el, idx, arr); }); return found ? index : -1; -} +}; /** @@ -7336,7 +6762,7 @@ ol.array.isSorted = function(arr, opt_func, opt_strict) { var res = compare(arr[index - 1], currentVal); return !(res > 0 || opt_strict && res === 0); }); -} +}; goog.provide('ol.ResolutionConstraint'); @@ -8894,7 +8320,6 @@ goog.require('ol.sphere.NORMAL'); * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or * `'us-ft'`. * @enum {string} - * @api stable */ ol.proj.Units = { DEGREES: 'degrees', @@ -8925,7 +8350,7 @@ ol.proj.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; * Projection definition class. One of these is created for each projection * supported in the application and stored in the {@link ol.proj} namespace. * You can use these in applications, but this is not required, as API params - * and options use {@link ol.proj.ProjectionLike} which means the simple string + * and options use {@link ol.ProjectionLike} which means the simple string * code will suffice. * * You can use {@link ol.proj.get} to retrieve the object for a particular @@ -9424,8 +8849,8 @@ ol.proj.addTransform = function(source, destination, transformFn) { * converts these into the functions used internally which also handle * extents and coordinate arrays. * - * @param {ol.proj.ProjectionLike} source Source projection. - * @param {ol.proj.ProjectionLike} destination Destination projection. + * @param {ol.ProjectionLike} source Source projection. + * @param {ol.ProjectionLike} destination Destination projection. * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform * function (that is, from the source projection to the destination * projection) that takes a {@link ol.Coordinate} as argument and returns @@ -9509,7 +8934,7 @@ ol.proj.removeTransform = function(source, destination) { * Transforms a coordinate from longitude/latitude to a different projection. * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e. * an array with longitude as 1st and latitude as 2nd element. - * @param {ol.proj.ProjectionLike=} opt_projection Target projection. The + * @param {ol.ProjectionLike=} opt_projection Target projection. The * default is Web Mercator, i.e. 'EPSG:3857'. * @return {ol.Coordinate} Coordinate projected to the target projection. * @api stable @@ -9523,7 +8948,7 @@ ol.proj.fromLonLat = function(coordinate, opt_projection) { /** * Transforms a coordinate to longitude/latitude. * @param {ol.Coordinate} coordinate Projected coordinate. - * @param {ol.proj.ProjectionLike=} opt_projection Projection of the coordinate. + * @param {ol.ProjectionLike=} opt_projection Projection of the coordinate. * The default is Web Mercator, i.e. 'EPSG:3857'. * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array * with longitude as 1st and latitude as 2nd element. @@ -9538,7 +8963,7 @@ ol.proj.toLonLat = function(coordinate, opt_projection) { /** * Fetches a Projection object for the code specified. * - * @param {ol.proj.ProjectionLike} projectionLike Either a code string which is + * @param {ol.ProjectionLike} projectionLike Either a code string which is * a combination of authority and identifier such as "EPSG:4326", or an * existing projection object, or undefined. * @return {ol.proj.Projection} Projection object, or null if not in list. @@ -9596,8 +9021,8 @@ ol.proj.equivalent = function(projection1, projection2) { * function to convert a coordinates array from the source projection to the * destination projection. * - * @param {ol.proj.ProjectionLike} source Source. - * @param {ol.proj.ProjectionLike} destination Destination. + * @param {ol.ProjectionLike} source Source. + * @param {ol.ProjectionLike} destination Destination. * @return {ol.TransformFunction} Transform function. * @api stable */ @@ -9683,8 +9108,8 @@ ol.proj.cloneTransform = function(input, opt_output, opt_dimension) { * geometry transforms. * * @param {ol.Coordinate} coordinate Coordinate. - * @param {ol.proj.ProjectionLike} source Source projection-like. - * @param {ol.proj.ProjectionLike} destination Destination projection-like. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. * @return {ol.Coordinate} Coordinate. * @api stable */ @@ -9699,8 +9124,8 @@ ol.proj.transform = function(coordinate, source, destination) { * returns a new extent (and does not modify the original). * * @param {ol.Extent} extent The extent to transform. - * @param {ol.proj.ProjectionLike} source Source projection-like. - * @param {ol.proj.ProjectionLike} destination Destination projection-like. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. * @return {ol.Extent} The transformed extent. * @api stable */ @@ -9741,7 +9166,6 @@ goog.require('ol.proj.Units'); * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, * `'GeometryCollection'`, `'Circle'`. * @enum {string} - * @api stable */ ol.geom.GeometryType = { POINT: 'Point', @@ -9761,7 +9185,6 @@ ol.geom.GeometryType = { * or measure ('M') coordinate is available. Supported values are `'XY'`, * `'XYZ'`, `'XYM'`, `'XYZM'`. * @enum {string} - * @api stable */ ol.geom.GeometryLayout = { XY: 'XY', @@ -9786,7 +9209,7 @@ ol.geom.GeometryLayout = { */ ol.geom.Geometry = function() { - goog.base(this); + ol.Object.call(this); /** * @private @@ -9819,7 +9242,7 @@ ol.geom.Geometry = function() { this.simplifiedGeometryRevision = 0; }; -goog.inherits(ol.geom.Geometry, ol.Object); +ol.inherits(ol.geom.Geometry, ol.Object); /** @@ -9979,9 +9402,9 @@ ol.geom.Geometry.prototype.translate = goog.abstractMethod; * If you do not want the geometry modified in place, first `clone()` it and * then use this function on the clone. * - * @param {ol.proj.ProjectionLike} source The current projection. Can be a + * @param {ol.ProjectionLike} source The current projection. Can be a * string identifier or a {@link ol.proj.Projection} object. - * @param {ol.proj.ProjectionLike} destination The desired projection. Can be a + * @param {ol.ProjectionLike} destination The desired projection. Can be a * string identifier or a {@link ol.proj.Projection} object. * @return {ol.geom.Geometry} This geometry. Note that original geometry is * modified in place. @@ -11851,7 +11274,7 @@ goog.vec.Mat4.getDiagonal = function(mat, vec, opt_diagonal) { /** * Sets the specified column with the supplied values. * - * @param {goog.vec.Mat4.AnyType} mat The matrix to recieve the values. + * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values. * @param {number} column The column index to set the values on. * @param {number} v0 The value for row 0. * @param {number} v1 The value for row 1. @@ -12613,7 +12036,7 @@ goog.vec.Mat4.makeFrustum = function(mat, left, right, bottom, top, near, far) { /** - * Makse the given 4x4 matrix perspective projection matrix given a + * Makes the given 4x4 matrix perspective projection matrix given a * field of view and aspect ratio. * * @param {goog.vec.Mat4.AnyType} mat The matrix. @@ -13217,7 +12640,7 @@ goog.require('ol.object'); */ ol.geom.SimpleGeometry = function() { - goog.base(this); + ol.geom.Geometry.call(this); /** * @protected @@ -13238,7 +12661,7 @@ ol.geom.SimpleGeometry = function() { this.flatCoordinates = null; }; -goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); +ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); /** @@ -14412,7 +13835,7 @@ goog.require('ol.geom.flat.simplify'); */ ol.geom.LinearRing = function(coordinates, opt_layout) { - goog.base(this); + ol.geom.SimpleGeometry.call(this); /** * @private @@ -14429,7 +13852,7 @@ ol.geom.LinearRing = function(coordinates, opt_layout) { this.setCoordinates(coordinates, opt_layout); }; -goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); +ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); /** @@ -14560,10 +13983,10 @@ goog.require('ol.math'); * @api stable */ ol.geom.Point = function(coordinates, opt_layout) { - goog.base(this); + ol.geom.SimpleGeometry.call(this); this.setCoordinates(coordinates, opt_layout); }; -goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry); +ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry); /** @@ -15258,7 +14681,7 @@ goog.require('ol.math'); */ ol.geom.Polygon = function(coordinates, opt_layout) { - goog.base(this); + ol.geom.SimpleGeometry.call(this); /** * @type {Array.} @@ -15305,7 +14728,7 @@ ol.geom.Polygon = function(coordinates, opt_layout) { this.setCoordinates(coordinates, opt_layout); }; -goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); +ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); /** @@ -15797,7 +15220,7 @@ ol.ViewHint = { * @api stable */ ol.View = function(opt_options) { - goog.base(this); + ol.Object.call(this); var options = opt_options || {}; /** @@ -15868,7 +15291,7 @@ ol.View = function(opt_options) { options.rotation !== undefined ? options.rotation : 0; this.setProperties(properties); }; -goog.inherits(ol.View, ol.Object); +ol.inherits(ol.View, ol.Object); /** @@ -15998,6 +15421,26 @@ ol.View.prototype.calculateExtent = function(size) { }; +/** + * Get the maximum resolution of the view. + * @return {number} The maximum resolution of the view. + * @api + */ +ol.View.prototype.getMaxResolution = function() { + return this.maxResolution_; +}; + + +/** + * Get the minimum resolution of the view. + * @return {number} The minimum resolution of the view. + * @api + */ +ol.View.prototype.getMinResolution = function() { + return this.minResolution_; +}; + + /** * Get the view projection. * @return {ol.proj.Projection} The projection of the view. @@ -17034,7 +16477,7 @@ ol.CollectionEventType = { */ ol.CollectionEvent = function(type, opt_element, opt_target) { - goog.base(this, type, opt_target); + ol.events.Event.call(this, type, opt_target); /** * The element that is added to or removed from the collection. @@ -17044,7 +16487,7 @@ ol.CollectionEvent = function(type, opt_element, opt_target) { this.element = opt_element; }; -goog.inherits(ol.CollectionEvent, ol.events.Event); +ol.inherits(ol.CollectionEvent, ol.events.Event); /** @@ -17072,7 +16515,7 @@ ol.CollectionProperty = { */ ol.Collection = function(opt_array) { - goog.base(this); + ol.Object.call(this); /** * @private @@ -17083,7 +16526,7 @@ ol.Collection = function(opt_array) { this.updateLength_(); }; -goog.inherits(ol.Collection, ol.Object); +ol.inherits(ol.Collection, ol.Object); /** @@ -18550,7 +17993,7 @@ goog.array.binarySearch_ = function( * goog.array.defaultCompare, which compares the elements using * the built in < and > operators. This will produce the expected behavior * for homogeneous arrays of String(s) and Number(s), unlike the native sort, - * but will give unpredictable results for heterogenous lists of strings and + * but will give unpredictable results for heterogeneous lists of strings and * numbers with different numbers of digits. * * This sort is not guaranteed to be stable. @@ -19092,6 +18535,26 @@ goog.array.copyByIndex = function(arr, index_arr) { return result; }; + +/** + * Maps each element of the input array into zero or more elements of the output + * array. + * + * @param {!IArrayLike|string} arr Array or array like object + * over which to iterate. + * @param {function(this:THIS, VALUE, number, ?): !Array} f The function + * to call for every element. This function takes 3 arguments (the element, + * the index and the array) and should return an array. The result will be + * used to extend a new array. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within f. + * @return {!Array} a new array with the concatenation of all arrays + * returned from f. + * @template THIS, VALUE, RESULT + */ +goog.array.concatMap = function(arr, f, opt_obj) { + return goog.array.concat.apply([], goog.array.map(arr, f, opt_obj)); +}; + // Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20746,6 +20209,32 @@ goog.labs.userAgent.util.extractVersionTuples = function(userAgent) { goog.provide('goog.object'); +/** + * Whether two values are not observably distinguishable. This + * correctly detects that 0 is not the same as -0 and two NaNs are + * practically equivalent. + * + * The implementation is as suggested by harmony:egal proposal. + * + * @param {*} v The first value to compare. + * @param {*} v2 The second value to compare. + * @return {boolean} Whether two values are not observably distinguishable. + * @see http://wiki.ecmascript.org/doku.php?id=harmony:egal + */ +goog.object.is = function(v, v2) { + if (v === v2) { + // 0 === -0, but they are not identical. + // We need the cast because the compiler requires that v2 is a + // number (although 1/v2 works with non-number). We cast to ? to + // stop the compiler from type-checking this statement. + return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2); + } + + // NaN is non-reflexive: NaN !== NaN, although they are identical. + return v !== v && v2 !== v2; +}; + + /** * Calls a function for each element in an object/map/hash. * @@ -20869,9 +20358,6 @@ goog.object.every = function(obj, f, opt_obj) { * @return {number} The number of key-value pairs in the object map. */ goog.object.getCount = function(obj) { - // JS1.5 has __count__ but it has been deprecated so it raises a warning... - // in other words do not use. Also __count__ only includes the fields on the - // actual object and not in the prototype chain. var rv = 0; for (var key in obj) { rv++; @@ -21210,7 +20696,7 @@ goog.object.equals = function(a, b) { /** - * Does a flat clone of the object. + * Returns a shallow clone of the object. * * @param {Object} obj Object to clone. * @return {!Object} Clone of the input object. @@ -21444,12 +20930,13 @@ goog.require('goog.string'); /** - * @return {boolean} Whether the user's browser is Opera. + * @return {boolean} Whether the user's browser is Opera. Note: Chromium + * based Opera (Opera 15+) is detected as Chrome to avoid unnecessary + * special casing. * @private */ goog.labs.userAgent.browser.matchOpera_ = function() { - return goog.labs.userAgent.util.matchUserAgent('Opera') || - goog.labs.userAgent.util.matchUserAgent('OPR'); + return goog.labs.userAgent.util.matchUserAgent('Opera'); }; @@ -21529,7 +21016,6 @@ goog.labs.userAgent.browser.matchIosWebview_ = function() { goog.labs.userAgent.browser.matchChrome_ = function() { return (goog.labs.userAgent.util.matchUserAgent('Chrome') || goog.labs.userAgent.util.matchUserAgent('CriOS')) && - !goog.labs.userAgent.browser.matchOpera_() && !goog.labs.userAgent.browser.matchEdge_(); }; @@ -21661,7 +21147,7 @@ goog.labs.userAgent.browser.getVersion = function() { if (goog.labs.userAgent.browser.isOpera()) { // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first. // Opera uses 'OPR' for more recent UAs. - return lookUpValueWithKeys(['Version', 'Opera', 'OPR']); + return lookUpValueWithKeys(['Version', 'Opera']); } // Check Edge before Chrome since it has Chrome in the string. @@ -22332,6 +21818,12 @@ goog.define('goog.userAgent.ASSUME_IPHONE', false); goog.define('goog.userAgent.ASSUME_IPAD', false); +/** + * @define {boolean} Whether the user agent is running on an iPod. + */ +goog.define('goog.userAgent.ASSUME_IPOD', false); + + /** * @type {boolean} * @private @@ -22339,7 +21831,8 @@ goog.define('goog.userAgent.ASSUME_IPAD', false); goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC || goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX || goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID || - goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD; + goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD || + goog.userAgent.ASSUME_IPOD; /** @@ -22434,6 +21927,15 @@ goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ? goog.labs.userAgent.platform.isIpad(); +/** + * Whether the user agent is running on an iPod. + * @type {boolean} + */ +goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPOD : + goog.labs.userAgent.platform.isIpod(); + + /** * @return {string} The string that describes the version number of the user * agent. @@ -22617,22160 +22119,19411 @@ goog.userAgent.DOCUMENT_MODE = (function() { 5); })(); -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +goog.provide('ol.dom'); + +goog.require('goog.asserts'); +goog.require('goog.userAgent'); +goog.require('goog.vec.Mat4'); +goog.require('ol'); + /** - * @fileoverview Browser capability checks for the dom package. - * + * Create an html canvas element and returns its 2d context. + * @param {number=} opt_width Canvas width. + * @param {number=} opt_height Canvas height. + * @return {CanvasRenderingContext2D} The context. */ +ol.dom.createCanvasContext2D = function(opt_width, opt_height) { + var canvas = document.createElement('CANVAS'); + if (opt_width) { + canvas.width = opt_width; + } + if (opt_height) { + canvas.height = opt_height; + } + return canvas.getContext('2d'); +}; -goog.provide('goog.dom.BrowserFeature'); +/** + * Detect 2d transform. + * Adapted from http://stackoverflow.com/q/5661671/130442 + * http://caniuse.com/#feat=transforms2d + * @return {boolean} + */ +ol.dom.canUseCssTransform = (function() { + var canUseCssTransform; + return function() { + if (canUseCssTransform === undefined) { + goog.asserts.assert(document.body, + 'document.body should not be null'); + goog.asserts.assert(ol.global.getComputedStyle, + 'getComputedStyle is required (unsupported browser?)'); -goog.require('goog.userAgent'); + var el = document.createElement('P'), + has2d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + document.body.appendChild(el); + for (var t in transforms) { + if (t in el.style) { + el.style[t] = 'translate(1px,1px)'; + has2d = ol.global.getComputedStyle(el).getPropertyValue( + transforms[t]); + } + } + document.body.removeChild(el); + + canUseCssTransform = (has2d && has2d !== 'none'); + } + return canUseCssTransform; + }; +}()); /** - * Enum of browser capabilities. - * @enum {boolean} + * Detect 3d transform. + * Adapted from http://stackoverflow.com/q/5661671/130442 + * http://caniuse.com/#feat=transforms3d + * @return {boolean} */ -goog.dom.BrowserFeature = { - /** - * Whether attributes 'name' and 'type' can be added to an element after it's - * created. False in Internet Explorer prior to version 9. - */ - CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: - !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9), +ol.dom.canUseCssTransform3D = (function() { + var canUseCssTransform3D; + return function() { + if (canUseCssTransform3D === undefined) { + goog.asserts.assert(document.body, + 'document.body should not be null'); + goog.asserts.assert(ol.global.getComputedStyle, + 'getComputedStyle is required (unsupported browser?)'); - /** - * Whether we can use element.children to access an element's Element - * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment - * nodes in the collection.) - */ - CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE || - goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) || - goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'), + var el = document.createElement('P'), + has3d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + document.body.appendChild(el); + for (var t in transforms) { + if (t in el.style) { + el.style[t] = 'translate3d(1px,1px,1px)'; + has3d = ol.global.getComputedStyle(el).getPropertyValue( + transforms[t]); + } + } + document.body.removeChild(el); - /** - * Opera, Safari 3, and Internet Explorer 9 all support innerText but they - * include text nodes in script and style tags. Not document-mode-dependent. - */ - CAN_USE_INNER_TEXT: - (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')), + canUseCssTransform3D = (has3d && has3d !== 'none'); + } + return canUseCssTransform3D; + }; +}()); - /** - * MSIE, Opera, and Safari>=4 support element.parentElement to access an - * element's parent if it is an Element. - */ - CAN_USE_PARENT_ELEMENT_PROPERTY: - goog.userAgent.IE || goog.userAgent.OPERA || goog.userAgent.WEBKIT, - /** - * Whether NoScope elements need a scoped element written before them in - * innerHTML. - * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1 - */ - INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE, +/** + * @param {Element} element Element. + * @param {string} value Value. + */ +ol.dom.setTransform = function(element, value) { + var style = element.style; + style.WebkitTransform = value; + style.MozTransform = value; + style.OTransform = value; + style.msTransform = value; + style.transform = value; - /** - * Whether we use legacy IE range API. - */ - LEGACY_IE_RANGES: - goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) + // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason + if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) { + element.style.transformOrigin = '0 0'; + } }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Defines the goog.dom.TagName enum. This enumerates - * all HTML tag names specified in either the the W3C HTML 4.01 index of - * elements or the HTML5 draft specification. - * - * References: - * http://www.w3.org/TR/html401/index/elements.html - * http://dev.w3.org/html5/spec/section-index.html - * + * @param {!Element} element Element. + * @param {goog.vec.Mat4.Number} transform Matrix. + * @param {number=} opt_precision Precision. */ -goog.provide('goog.dom.TagName'); +ol.dom.transformElement2D = function(element, transform, opt_precision) { + // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer + // matrix3d() + var i; + if (ol.dom.canUseCssTransform3D()) { + var value3D; + if (opt_precision !== undefined) { + /** @type {Array.} */ + var strings3D = new Array(16); + for (i = 0; i < 16; ++i) { + strings3D[i] = transform[i].toFixed(opt_precision); + } + value3D = strings3D.join(','); + } else { + value3D = transform.join(','); + } + ol.dom.setTransform(element, 'matrix3d(' + value3D + ')'); + } else if (ol.dom.canUseCssTransform()) { + /** @type {Array.} */ + var transform2D = [ + goog.vec.Mat4.getElement(transform, 0, 0), + goog.vec.Mat4.getElement(transform, 1, 0), + goog.vec.Mat4.getElement(transform, 0, 1), + goog.vec.Mat4.getElement(transform, 1, 1), + goog.vec.Mat4.getElement(transform, 0, 3), + goog.vec.Mat4.getElement(transform, 1, 3) + ]; + var value2D; + if (opt_precision !== undefined) { + /** @type {Array.} */ + var strings2D = new Array(6); + for (i = 0; i < 6; ++i) { + strings2D[i] = transform2D[i].toFixed(opt_precision); + } + value2D = strings2D.join(','); + } else { + value2D = transform2D.join(','); + } + ol.dom.setTransform(element, 'matrix(' + value2D + ')'); + } else { + element.style.left = + Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px'; + element.style.top = + Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px'; -/** - * Enum of all html tag names specified by the W3C HTML4.01 and HTML5 - * specifications. - * @enum {string} - */ -goog.dom.TagName = { - A: 'A', - ABBR: 'ABBR', - ACRONYM: 'ACRONYM', - ADDRESS: 'ADDRESS', - APPLET: 'APPLET', - AREA: 'AREA', - ARTICLE: 'ARTICLE', - ASIDE: 'ASIDE', - AUDIO: 'AUDIO', - B: 'B', - BASE: 'BASE', - BASEFONT: 'BASEFONT', - BDI: 'BDI', - BDO: 'BDO', - BIG: 'BIG', - BLOCKQUOTE: 'BLOCKQUOTE', - BODY: 'BODY', - BR: 'BR', - BUTTON: 'BUTTON', - CANVAS: 'CANVAS', - CAPTION: 'CAPTION', - CENTER: 'CENTER', - CITE: 'CITE', - CODE: 'CODE', - COL: 'COL', - COLGROUP: 'COLGROUP', - COMMAND: 'COMMAND', - DATA: 'DATA', - DATALIST: 'DATALIST', - DD: 'DD', - DEL: 'DEL', - DETAILS: 'DETAILS', - DFN: 'DFN', - DIALOG: 'DIALOG', - DIR: 'DIR', - DIV: 'DIV', - DL: 'DL', - DT: 'DT', - EM: 'EM', - EMBED: 'EMBED', - FIELDSET: 'FIELDSET', - FIGCAPTION: 'FIGCAPTION', - FIGURE: 'FIGURE', - FONT: 'FONT', - FOOTER: 'FOOTER', - FORM: 'FORM', - FRAME: 'FRAME', - FRAMESET: 'FRAMESET', - H1: 'H1', - H2: 'H2', - H3: 'H3', - H4: 'H4', - H5: 'H5', - H6: 'H6', - HEAD: 'HEAD', - HEADER: 'HEADER', - HGROUP: 'HGROUP', - HR: 'HR', - HTML: 'HTML', - I: 'I', - IFRAME: 'IFRAME', - IMG: 'IMG', - INPUT: 'INPUT', - INS: 'INS', - ISINDEX: 'ISINDEX', - KBD: 'KBD', - KEYGEN: 'KEYGEN', - LABEL: 'LABEL', - LEGEND: 'LEGEND', - LI: 'LI', - LINK: 'LINK', - MAP: 'MAP', - MARK: 'MARK', - MATH: 'MATH', - MENU: 'MENU', - META: 'META', - METER: 'METER', - NAV: 'NAV', - NOFRAMES: 'NOFRAMES', - NOSCRIPT: 'NOSCRIPT', - OBJECT: 'OBJECT', - OL: 'OL', - OPTGROUP: 'OPTGROUP', - OPTION: 'OPTION', - OUTPUT: 'OUTPUT', - P: 'P', - PARAM: 'PARAM', - PRE: 'PRE', - PROGRESS: 'PROGRESS', - Q: 'Q', - RP: 'RP', - RT: 'RT', - RUBY: 'RUBY', - S: 'S', - SAMP: 'SAMP', - SCRIPT: 'SCRIPT', - SECTION: 'SECTION', - SELECT: 'SELECT', - SMALL: 'SMALL', - SOURCE: 'SOURCE', - SPAN: 'SPAN', - STRIKE: 'STRIKE', - STRONG: 'STRONG', - STYLE: 'STYLE', - SUB: 'SUB', - SUMMARY: 'SUMMARY', - SUP: 'SUP', - SVG: 'SVG', - TABLE: 'TABLE', - TBODY: 'TBODY', - TD: 'TD', - TEMPLATE: 'TEMPLATE', - TEXTAREA: 'TEXTAREA', - TFOOT: 'TFOOT', - TH: 'TH', - THEAD: 'THEAD', - TIME: 'TIME', - TITLE: 'TITLE', - TR: 'TR', - TRACK: 'TRACK', - TT: 'TT', - U: 'U', - UL: 'UL', - VAR: 'VAR', - VIDEO: 'VIDEO', - WBR: 'WBR' + // TODO: Add scaling here. This isn't quite as simple as multiplying + // width/height, because that only changes the container size, not the + // content size. + } }; -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for HTML element tag names. + * Get the current computed width for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerWidth(true)`. + * @param {!Element} element Element. + * @return {number} The width. */ -goog.provide('goog.dom.tags'); +ol.dom.outerWidth = function(element) { + var width = element.offsetWidth; + var style = element.currentStyle || ol.global.getComputedStyle(element); + width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); -goog.require('goog.object'); + return width; +}; /** - * The void elements specified by - * http://www.w3.org/TR/html-markup/syntax.html#void-elements. - * @const @private {!Object} + * Get the current computed height for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerHeight(true)`. + * @param {!Element} element Element. + * @return {number} The height. */ -goog.dom.tags.VOID_TAGS_ = goog.object.createSet( - 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', - 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'); +ol.dom.outerHeight = function(element) { + var height = element.offsetHeight; + var style = element.currentStyle || ol.global.getComputedStyle(element); + height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); + return height; +}; /** - * Checks whether the tag is void (with no contents allowed and no legal end - * tag), for example 'br'. - * @param {string} tagName The tag name in lower case. - * @return {boolean} + * @param {Node} newNode Node to replace old node + * @param {Node} oldNode The node to be replaced */ -goog.dom.tags.isVoidTag = function(tagName) { - return goog.dom.tags.VOID_TAGS_[tagName] === true; +ol.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); + } }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -goog.provide('goog.string.TypedString'); - - - /** - * Wrapper for strings that conform to a data type or language. - * - * Implementations of this interface are wrappers for strings, and typically - * associate a type contract with the wrapped string. Concrete implementations - * of this interface may choose to implement additional run-time type checking, - * see for example {@code goog.html.SafeHtml}. If available, client code that - * needs to ensure type membership of an object should use the type's function - * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}. - * @interface + * @param {Node} node The node to remove. + * @returns {Node} The node that was removed or null. */ -goog.string.TypedString = function() {}; - +ol.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; /** - * Interface marker of the TypedString interface. - * - * This property can be used to determine at runtime whether or not an object - * implements this interface. All implementations of this interface set this - * property to {@code true}. - * @type {boolean} + * @param {Node} node The node to remove the children from. */ -goog.string.TypedString.prototype.implementsGoogStringTypedString; +ol.dom.removeChildren = function(node) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } +}; + +goog.provide('ol.MapEvent'); +goog.provide('ol.MapEventType'); + +goog.require('ol.events.Event'); /** - * Retrieves this wrapped string's value. - * @return {!string} The wrapped string's value. + * @enum {string} */ -goog.string.TypedString.prototype.getTypedStringValue; - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +ol.MapEventType = { -goog.provide('goog.string.Const'); + /** + * Triggered after a map frame is rendered. + * @event ol.MapEvent#postrender + * @api + */ + POSTRENDER: 'postrender', -goog.require('goog.asserts'); -goog.require('goog.string.TypedString'); + /** + * Triggered after the map is moved. + * @event ol.MapEvent#moveend + * @api stable + */ + MOVEEND: 'moveend' +}; /** - * Wrapper for compile-time-constant strings. - * - * Const is a wrapper for strings that can only be created from program - * constants (i.e., string literals). This property relies on a custom Closure - * compiler check that {@code goog.string.Const.from} is only invoked on - * compile-time-constant expressions. - * - * Const is useful in APIs whose correct and secure use requires that certain - * arguments are not attacker controlled: Compile-time constants are inherently - * under the control of the application and not under control of external - * attackers, and hence are safe to use in such contexts. + * @classdesc + * Events emitted as map events are instances of this type. + * See {@link ol.Map} for which events trigger a map event. * - * Instances of this type must be created via its factory method - * {@code goog.string.Const.from} and not by invoking its constructor. The - * constructor intentionally takes no parameters and the type is immutable; - * hence only a default instance corresponding to the empty string can be - * obtained via constructor invocation. - * - * @see goog.string.Const#from * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} + * @extends {ol.events.Event} + * @implements {oli.MapEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {?olx.FrameState=} opt_frameState Frame state. */ -goog.string.Const = function() { +ol.MapEvent = function(type, map, opt_frameState) { + + ol.events.Event.call(this, type); + /** - * The wrapped value of this Const object. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} + * The map where the event occurred. + * @type {ol.Map} + * @api stable */ - this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = ''; + this.map = map; /** - * A type marker used to implement additional run-time type checking. - * @see goog.string.Const#unwrap - * @const - * @private + * The frame state at the time of the event. + * @type {?olx.FrameState} + * @api */ - this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ = - goog.string.Const.TYPE_MARKER_; -}; - - -/** - * @override - * @const - */ -goog.string.Const.prototype.implementsGoogStringTypedString = true; - + this.frameState = opt_frameState !== undefined ? opt_frameState : null; -/** - * Returns this Const's value a string. - * - * IMPORTANT: In code where it is security-relevant that an object's type is - * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap} - * instead of this method. - * - * @see goog.string.Const#unwrap - * @override - */ -goog.string.Const.prototype.getTypedStringValue = function() { - return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_; }; +ol.inherits(ol.MapEvent, ol.events.Event); +goog.provide('ol.control.Control'); -/** - * Returns a debug-string representation of this value. - * - * To obtain the actual string value wrapped inside an object of this type, - * use {@code goog.string.Const.unwrap}. - * - * @see goog.string.Const#unwrap - * @override - */ -goog.string.Const.prototype.toString = function() { - return 'Const{' + - this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ + - '}'; -}; - - -/** - * Performs a runtime check that the provided object is indeed an instance - * of {@code goog.string.Const}, and returns its value. - * @param {!goog.string.Const} stringConst The object to extract from. - * @return {string} The Const object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.string.Const.unwrap = function(stringConst) { - // Perform additional run-time type-checking to ensure that stringConst is - // indeed an instance of the expected type. This provides some additional - // protection against security bugs due to application code that disables type - // checks. - if (stringConst instanceof goog.string.Const && - stringConst.constructor === goog.string.Const && - stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ === - goog.string.Const.TYPE_MARKER_) { - return stringConst - .stringConstValueWithSecurityContract__googStringSecurityPrivate_; - } else { - goog.asserts.fail( - 'expected object of type Const, got \'' + stringConst + '\''); - return 'type_error:Const'; - } -}; +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.MapEventType'); +goog.require('ol.Object'); +goog.require('ol.dom'); /** - * Creates a Const object from a compile-time constant string. - * - * It is illegal to invoke this function on an expression whose - * compile-time-contant value cannot be determined by the Closure compiler. - * - * Correct invocations include, - *
- *   var s = goog.string.Const.from('hello');
- *   var t = goog.string.Const.from('hello' + 'world');
- * 
+ * @classdesc + * A control is a visible widget with a DOM element in a fixed position on the + * screen. They can involve user input (buttons), or be informational only; + * the position is determined using CSS. By default these are placed in the + * container with CSS class name `ol-overlaycontainer-stopevent`, but can use + * any outside DOM element. * - * In contrast, the following are illegal: - *
- *   var s = goog.string.Const.from(getHello());
- *   var t = goog.string.Const.from('hello' + world);
- * 
+ * This is the base class for controls. You can use it for simple custom + * controls by creating the element with listeners, creating an instance: + * ```js + * var myControl = new ol.control.Control({element: myElement}); + * ``` + * and then adding this to the map. * - * TODO(xtof): Compile-time checks that this function is only called - * with compile-time constant expressions. + * The main advantage of having this as a control rather than a simple separate + * DOM element is that preventing propagation is handled for you. Controls + * will also be `ol.Object`s in a `ol.Collection`, so you can use their + * methods. * - * @param {string} s A constant string from which to create a Const. - * @return {!goog.string.Const} A Const object initialized to stringConst. - */ -goog.string.Const.from = function(s) { - return goog.string.Const.create__googStringSecurityPrivate_(s); -}; - - -/** - * Type marker for the Const type, used to implement additional run-time - * type checking. - * @const {!Object} - * @private - */ -goog.string.Const.TYPE_MARKER_ = {}; - - -/** - * Utility method to create Const instances. - * @param {string} s The string to initialize the Const object with. - * @return {!goog.string.Const} The initialized Const object. - * @private - */ -goog.string.Const.create__googStringSecurityPrivate_ = function(s) { - var stringConst = new goog.string.Const(); - stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = - s; - return stringConst; -}; - -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview The SafeStyle type and its builders. + * You can also extend this base for your own control class. See + * examples/custom-controls for an example of how to do this. * - * TODO(xtof): Link to document stating type contract. + * @constructor + * @extends {ol.Object} + * @implements {oli.control.Control} + * @param {olx.control.ControlOptions} options Control options. + * @api stable */ +ol.control.Control = function(options) { -goog.provide('goog.html.SafeStyle'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - - + ol.Object.call(this); -/** - * A string-like object which represents a sequence of CSS declarations - * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...}) - * and that carries the security type contract that its value, as a string, - * will not cause untrusted script execution (XSS) when evaluated as CSS in a - * browser. - * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeStyle.create} or - * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty string - * can be obtained via constructor invocation. - * - * A SafeStyle's string representation ({@link #getTypedStringValue()}) can - * safely: - *
    - *
  • Be interpolated as the entire content of a *quoted* HTML style - * attribute, or before already existing properties. The SafeStyle string - * *must be HTML-attribute-escaped* (where " and ' are escaped) before - * interpolation. - *
  • Be interpolated as the entire content of a {}-wrapped block within a - * stylesheet, or before already existing properties. The SafeStyle string - * should not be escaped before interpolation. SafeStyle's contract also - * guarantees that the string will not be able to introduce new properties - * or elide existing ones. - *
  • Be assigned to the style property of a DOM node. The SafeStyle string - * should not be escaped before being assigned to the property. - *
- * - * A SafeStyle may never contain literal angle brackets. Otherwise, it could - * be unsafe to place a SafeStyle into a <style> tag (where it can't - * be HTML escaped). For example, if the SafeStyle containing - * "{@code font: 'foo <style/><script>evil</script>'}" were - * interpolated within a <style> tag, this would then break out of the - * style context into HTML. - * - * A SafeStyle may contain literal single or double quotes, and as such the - * entire style string must be escaped when used in a style attribute (if - * this were not the case, the string could contain a matching quote that - * would escape from the style attribute). - * - * Values of this type must be composable, i.e. for any two values - * {@code style1} and {@code style2} of this type, - * {@code goog.html.SafeStyle.unwrap(style1) + - * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies - * the SafeStyle type constraint. This requirement implies that for any value - * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must - * not end in a "property value" or "property name" context. For example, - * a value of {@code background:url("} or {@code font-} would not satisfy the - * SafeStyle contract. This is because concatenating such strings with a - * second value that itself does not contain unsafe CSS can result in an - * overall string that does. For example, if {@code javascript:evil())"} is - * appended to {@code background:url("}, the resulting string may result in - * the execution of a malicious script. - * - * TODO(user): Consider whether we should implement UTF-8 interchange - * validity checks and blacklisting of newlines (including Unicode ones) and - * other whitespace characters (\t, \f). Document here if so and also update - * SafeStyle.fromConstant(). - * - * The following example values comply with this type's contract: - *
    - *
  • width: 1em;
    - *
  • height:1em;
    - *
  • width: 1em;height: 1em;
    - *
  • background:url('http://url');
    - *
- * In addition, the empty string is safe for use in a CSS attribute. - * - * The following example values do NOT comply with this type's contract: - *
    - *
  • background: red
    (missing a trailing semi-colon) - *
  • background:
    (missing a value and a trailing semi-colon) - *
  • 1em
    (missing an attribute name, which provides context for - * the value) - *
- * - * @see goog.html.SafeStyle#create - * @see goog.html.SafeStyle#fromConstant - * @see http://www.w3.org/TR/css3-syntax/ - * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} - */ -goog.html.SafeStyle = function() { /** - * The contained value of this SafeStyle. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} + * @protected + * @type {Element} */ - this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = ''; + this.element = options.element ? options.element : null; /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeStyle#unwrap - * @const * @private + * @type {Element} */ - this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; -}; - - -/** - * @override - * @const - */ -goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true; + this.target_ = null; + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; -/** - * Type marker for the SafeStyle type, used to implement additional - * run-time type checking. - * @const {!Object} - * @private - */ -goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + /** + * @protected + * @type {!Array.} + */ + this.listenerKeys = []; + /** + * @type {function(ol.MapEvent)} + */ + this.render = options.render ? options.render : ol.nullFunction; -/** - * Creates a SafeStyle object from a compile-time constant string. - * - * {@code style} should be in the format - * {@code name: value; [name: value; ...]} and must not have any < or > - * characters in it. This is so that SafeStyle's contract is preserved, - * allowing the SafeStyle to correctly be interpreted as a sequence of CSS - * declarations and without affecting the syntactic structure of any - * surrounding CSS and HTML. - * - * This method performs basic sanity checks on the format of {@code style} - * but does not constrain the format of {@code name} and {@code value}, except - * for disallowing tag characters. - * - * @param {!goog.string.Const} style A compile-time-constant string from which - * to create a SafeStyle. - * @return {!goog.html.SafeStyle} A SafeStyle object initialized to - * {@code style}. - */ -goog.html.SafeStyle.fromConstant = function(style) { - var styleString = goog.string.Const.unwrap(style); - if (styleString.length === 0) { - return goog.html.SafeStyle.EMPTY; + if (options.target) { + this.setTarget(options.target); } - goog.html.SafeStyle.checkStyle_(styleString); - goog.asserts.assert( - goog.string.endsWith(styleString, ';'), - 'Last character of style string is not \';\': ' + styleString); - goog.asserts.assert( - goog.string.contains(styleString, ':'), - 'Style string must contain at least one \':\', to ' + - 'specify a "name: value" pair: ' + styleString); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - styleString); -}; - -/** - * Checks if the style definition is valid. - * @param {string} style - * @private - */ -goog.html.SafeStyle.checkStyle_ = function(style) { - goog.asserts.assert( - !/[<>]/.test(style), 'Forbidden characters in style string: ' + style); }; +ol.inherits(ol.control.Control, ol.Object); /** - * Returns this SafeStyle's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of - * this method. If in doubt, assume that it's security relevant. In particular, - * note that goog.html functions which return a goog.html type do not guarantee - * the returned instance is of the right type. For example: - * - *
- * var fakeSafeHtml = new String('fake');
- * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
- * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
- * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
- * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
- * // instanceof goog.html.SafeHtml.
- * 
- * - * @see goog.html.SafeStyle#unwrap - * @override + * @inheritDoc */ -goog.html.SafeStyle.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeStyleWrappedValue_; +ol.control.Control.prototype.disposeInternal = function() { + ol.dom.removeNode(this.element); + ol.Object.prototype.disposeInternal.call(this); }; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeStyle, use - * {@code goog.html.SafeStyle.unwrap}. - * - * @see goog.html.SafeStyle#unwrap - * @override - */ - goog.html.SafeStyle.prototype.toString = function() { - return 'SafeStyle{' + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + - '}'; - }; -} - - /** - * Performs a runtime check that the provided object is indeed a - * SafeStyle object, and returns its value. - * - * @param {!goog.html.SafeStyle} safeStyle The object to extract from. - * @return {string} The safeStyle object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeStyle.unwrap = function(safeStyle) { - // Perform additional Run-time type-checking to ensure that - // safeStyle is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeStyle instanceof goog.html.SafeStyle && - safeStyle.constructor === goog.html.SafeStyle && - safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_; - } else { - goog.asserts.fail('expected object of type SafeStyle, got \'' + - safeStyle + '\' of type ' + goog.typeOf(safeStyle)); - return 'type_error:SafeStyle'; - } + * Get the map associated with this control. + * @return {ol.Map} Map. + * @api stable + */ +ol.control.Control.prototype.getMap = function() { + return this.map_; }; /** - * Package-internal utility method to create SafeStyle instances. - * - * @param {string} style The string to initialize the SafeStyle object with. - * @return {!goog.html.SafeStyle} The initialized SafeStyle object. - * @package + * Remove the control from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + * @api stable */ -goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = function( - style) { - return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style); +ol.control.Control.prototype.setMap = function(map) { + if (this.map_) { + ol.dom.removeNode(this.element); + } + for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { + ol.events.unlistenByKey(this.listenerKeys[i]); + } + this.listenerKeys.length = 0; + this.map_ = map; + if (this.map_) { + var target = this.target_ ? + this.target_ : map.getOverlayContainerStopEvent(); + target.appendChild(this.element); + if (this.render !== ol.nullFunction) { + this.listenerKeys.push(ol.events.listen(map, + ol.MapEventType.POSTRENDER, this.render, this)); + } + map.render(); + } }; /** - * Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This - * method exists only so that the compiler can dead code eliminate static - * fields (like EMPTY) when they're not accessed. - * @param {string} style - * @return {!goog.html.SafeStyle} - * @private + * This function is used to set a target element for the control. It has no + * effect if it is called after the control has been added to the map (i.e. + * after `setMap` is called on the control). If no `target` is set in the + * options passed to the control constructor and if `setTarget` is not called + * then the control is added to the map's overlay container. + * @param {Element|string} target Target. + * @api */ -goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function( - style) { - this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style; - return this; +ol.control.Control.prototype.setTarget = function(target) { + this.target_ = typeof target === 'string' ? + document.getElementById(target) : + target; }; - -/** - * A SafeStyle instance corresponding to the empty string. - * @const {!goog.html.SafeStyle} - */ -goog.html.SafeStyle.EMPTY = - goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(''); +goog.provide('ol.css'); /** - * The innocuous string generated by goog.html.SafeUrl.create when passed - * an unsafe value. - * @const {string} + * The CSS class for hidden feature. + * + * @const + * @type {string} */ -goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez'; +ol.css.CLASS_HIDDEN = 'ol-hidden'; /** - * Mapping of property names to their values. - * @typedef {!Object} + * The CSS class that we'll give the DOM elements to have them unselectable. + * + * @const + * @type {string} */ -goog.html.SafeStyle.PropertyMap; +ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; /** - * Creates a new SafeStyle object from the properties specified in the map. - * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to - * their values, for example {'margin': '1px'}. Names must consist of - * [-_a-zA-Z0-9]. Values might be strings consisting of - * [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced. - * Other values must be wrapped in goog.string.Const. Null value causes - * skipping the property. - * @return {!goog.html.SafeStyle} - * @throws {Error} If invalid name is provided. - * @throws {goog.asserts.AssertionError} If invalid value is provided. With - * disabled assertions, invalid value is replaced by - * goog.html.SafeStyle.INNOCUOUS_STRING. + * The CSS class for unsupported feature. + * + * @const + * @type {string} */ -goog.html.SafeStyle.create = function(map) { - var style = ''; - for (var name in map) { - if (!/^[-_a-zA-Z0-9]+$/.test(name)) { - throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name); - } - var value = map[name]; - if (value == null) { - continue; - } - if (value instanceof goog.string.Const) { - value = goog.string.Const.unwrap(value); - // These characters can be used to change context and we don't want that - // even with const values. - goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].'); - } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) { - goog.asserts.fail( - 'String value allows only [-,."\'%_!# a-zA-Z0-9], rgb() and ' + - 'rgba(), got: ' + value); - value = goog.html.SafeStyle.INNOCUOUS_STRING; - } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) { - goog.asserts.fail('String value requires balanced quotes, got: ' + value); - value = goog.html.SafeStyle.INNOCUOUS_STRING; - } - style += name + ':' + value + ';'; - } - if (!style) { - return goog.html.SafeStyle.EMPTY; - } - goog.html.SafeStyle.checkStyle_(style); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); -}; +ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; /** - * Checks that quotes (" and ') are properly balanced inside a string. Assumes - * that neither escape (\) nor any other character that could result in - * breaking out of a string parsing context are allowed; - * see http://www.w3.org/TR/css3-syntax/#string-token-diagram. - * @param {string} value Untrusted CSS property value. - * @return {boolean} True if property value is safe with respect to quote - * balancedness. - * @private + * The CSS class for controls. + * + * @const + * @type {string} */ -goog.html.SafeStyle.hasBalancedQuotes_ = function(value) { - var outsideSingle = true; - var outsideDouble = true; - for (var i = 0; i < value.length; i++) { - var c = value.charAt(i); - if (c == "'" && outsideDouble) { - outsideSingle = !outsideSingle; - } else if (c == '"' && outsideSingle) { - outsideDouble = !outsideDouble; - } - } - return outsideSingle && outsideDouble; -}; +ol.css.CLASS_CONTROL = 'ol-control'; +goog.provide('ol.structs.LRUCache'); -// Keep in sync with the error string in create(). -/** - * Regular expression for safe values. - * - * Quotes (" and ') are allowed, but a check must be done elsewhere to ensure - * they're balanced. - * - * ',' allows multiple values to be assigned to the same property - * (e.g. background-attachment or font-family) and hence could allow - * multiple values to get injected, but that should pose no risk of XSS. - * - * The rgb() and rgba() expression checks only for XSS safety, not for CSS - * validity. - * @const {!RegExp} - * @private - */ -goog.html.SafeStyle.VALUE_RE_ = - /^([-,."'%_!# a-zA-Z0-9]+|(?:rgb|hsl)a?\([0-9.%, ]+\))$/; +goog.require('goog.asserts'); +goog.require('ol.object'); /** - * Creates a new SafeStyle object by concatenating the values. - * @param {...(!goog.html.SafeStyle|!Array)} var_args - * SafeStyles to concatenate. - * @return {!goog.html.SafeStyle} + * Implements a Least-Recently-Used cache where the keys do not conflict with + * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring + * items from the cache is the responsibility of the user. + * @constructor + * @struct + * @template T */ -goog.html.SafeStyle.concat = function(var_args) { - var style = ''; +ol.structs.LRUCache = function() { /** - * @param {!goog.html.SafeStyle|!Array} argument + * @private + * @type {number} */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - style += goog.html.SafeStyle.unwrap(argument); - } - }; - - goog.array.forEach(arguments, addArgument); - if (!style) { - return goog.html.SafeStyle.EMPTY; - } - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); -}; - -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview The SafeStyleSheet type and its builders. - * - * TODO(xtof): Link to document stating type contract. - */ + this.count_ = 0; -goog.provide('goog.html.SafeStyleSheet'); + /** + * @private + * @type {!Object.} + */ + this.entries_ = {}; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - - - -/** - * A string-like object which represents a CSS style sheet and that carries the - * security type contract that its value, as a string, will not cause untrusted - * script execution (XSS) when evaluated as CSS in a browser. - * - * Instances of this type must be created via the factory method - * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty string - * can be obtained via constructor invocation. - * - * A SafeStyleSheet's string representation can safely be interpolated as the - * content of a style element within HTML. The SafeStyleSheet string should - * not be escaped before interpolation. - * - * Values of this type must be composable, i.e. for any two values - * {@code styleSheet1} and {@code styleSheet2} of this type, - * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) + - * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that - * satisfies the SafeStyleSheet type constraint. This requirement implies that - * for any value {@code styleSheet} of this type, - * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in - * "beginning of rule" context. - - * A SafeStyleSheet can be constructed via security-reviewed unchecked - * conversions. In this case producers of SafeStyleSheet must ensure themselves - * that the SafeStyleSheet does not contain unsafe script. Note in particular - * that {@code <} is dangerous, even when inside CSS strings, and so should - * always be forbidden or CSS-escaped in user controlled input. For example, if - * {@code </style><script>evil</script>"} were interpolated - * inside a CSS string, it would break out of the context of the original - * style element and {@code evil} would execute. Also note that within an HTML - * style (raw text) element, HTML character references, such as - * {@code &lt;}, are not allowed. See - * - http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements - * (similar considerations apply to the style element). - * - * @see goog.html.SafeStyleSheet#fromConstant - * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} - */ -goog.html.SafeStyleSheet = function() { /** - * The contained value of this SafeStyleSheet. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} + * @private + * @type {?ol.LRUCacheEntry} */ - this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = ''; + this.oldest_ = null; /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeStyleSheet#unwrap - * @const * @private + * @type {?ol.LRUCacheEntry} */ - this.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + this.newest_ = null; + }; /** - * @override - * @const + * FIXME empty description for jsdoc */ -goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true; +ol.structs.LRUCache.prototype.assertValid = function() { + if (this.count_ === 0) { + goog.asserts.assert(ol.object.isEmpty(this.entries_), + 'entries must be an empty object (count = 0)'); + goog.asserts.assert(!this.oldest_, + 'oldest must be null (count = 0)'); + goog.asserts.assert(!this.newest_, + 'newest must be null (count = 0)'); + } else { + goog.asserts.assert(Object.keys(this.entries_).length == this.count_, + 'number of entries matches count'); + goog.asserts.assert(this.oldest_, + 'we have an oldest entry'); + goog.asserts.assert(!this.oldest_.older, + 'no entry is older than oldest'); + goog.asserts.assert(this.newest_, + 'we have a newest entry'); + goog.asserts.assert(!this.newest_.newer, + 'no entry is newer than newest'); + var i, entry; + var older = null; + i = 0; + for (entry = this.oldest_; entry; entry = entry.newer) { + goog.asserts.assert(entry.older === older, + 'entry.older links to correct older'); + older = entry; + ++i; + } + goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); + var newer = null; + i = 0; + for (entry = this.newest_; entry; entry = entry.older) { + goog.asserts.assert(entry.newer === newer, + 'entry.newer links to correct newer'); + newer = entry; + ++i; + } + goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); + } +}; /** - * Type marker for the SafeStyleSheet type, used to implement additional - * run-time type checking. - * @const {!Object} - * @private + * FIXME empty description for jsdoc */ -goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.structs.LRUCache.prototype.clear = function() { + this.count_ = 0; + this.entries_ = {}; + this.oldest_ = null; + this.newest_ = null; +}; /** - * Creates a new SafeStyleSheet object by concatenating values. - * @param {...(!goog.html.SafeStyleSheet|!Array)} - * var_args Values to concatenate. - * @return {!goog.html.SafeStyleSheet} + * @param {string} key Key. + * @return {boolean} Contains key. */ -goog.html.SafeStyleSheet.concat = function(var_args) { - var result = ''; - - /** - * @param {!goog.html.SafeStyleSheet|!Array} - * argument - */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - result += goog.html.SafeStyleSheet.unwrap(argument); - } - }; - - goog.array.forEach(arguments, addArgument); - return goog.html.SafeStyleSheet - .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result); +ol.structs.LRUCache.prototype.containsKey = function(key) { + return this.entries_.hasOwnProperty(key); }; /** - * Creates a SafeStyleSheet object from a compile-time constant string. - * - * {@code styleSheet} must not have any < characters in it, so that - * the syntactic structure of the surrounding HTML is not affected. - * - * @param {!goog.string.Const} styleSheet A compile-time-constant string from - * which to create a SafeStyleSheet. - * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to - * {@code styleSheet}. + * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function + * to call for every entry from the oldest to the newer. This function takes + * 3 arguments (the entry value, the entry key and the LRUCache object). + * The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S */ -goog.html.SafeStyleSheet.fromConstant = function(styleSheet) { - var styleSheetString = goog.string.Const.unwrap(styleSheet); - if (styleSheetString.length === 0) { - return goog.html.SafeStyleSheet.EMPTY; +ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { + var entry = this.oldest_; + while (entry) { + f.call(opt_this, entry.value_, entry.key_, this); + entry = entry.newer; } - // > is a valid character in CSS selectors and there's no strict need to - // block it if we already block <. - goog.asserts.assert( - !goog.string.contains(styleSheetString, '<'), - "Forbidden '<' character in style sheet string: " + styleSheetString); - return goog.html.SafeStyleSheet - .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString); }; /** - * Returns this SafeStyleSheet's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap} - * instead of this method. If in doubt, assume that it's security relevant. In - * particular, note that goog.html functions which return a goog.html type do - * not guarantee the returned instance is of the right type. For example: - * - *
- * var fakeSafeHtml = new String('fake');
- * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
- * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
- * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
- * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
- * // instanceof goog.html.SafeHtml.
- * 
- * - * @see goog.html.SafeStyleSheet#unwrap - * @override + * @param {string} key Key. + * @return {T} Value. */ -goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; -}; - - -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeStyleSheet, use - * {@code goog.html.SafeStyleSheet.unwrap}. - * - * @see goog.html.SafeStyleSheet#unwrap - * @override - */ - goog.html.SafeStyleSheet.prototype.toString = function() { - return 'SafeStyleSheet{' + - this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}'; - }; -} - - -/** - * Performs a runtime check that the provided object is indeed a - * SafeStyleSheet object, and returns its value. - * - * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from. - * @return {string} The safeStyleSheet object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) { - // Perform additional Run-time type-checking to ensure that - // safeStyleSheet is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeStyleSheet instanceof goog.html.SafeStyleSheet && - safeStyleSheet.constructor === goog.html.SafeStyleSheet && - safeStyleSheet - .SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; +ol.structs.LRUCache.prototype.get = function(key) { + var entry = this.entries_[key]; + goog.asserts.assert(entry !== undefined, 'an entry exists for key %s', key); + if (entry === this.newest_) { + return entry.value_; + } else if (entry === this.oldest_) { + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer); + this.oldest_.older = null; } else { - goog.asserts.fail('expected object of type SafeStyleSheet, got \'' + - safeStyleSheet + '\' of type ' + goog.typeOf(safeStyleSheet)); - return 'type_error:SafeStyleSheet'; + entry.newer.older = entry.older; + entry.older.newer = entry.newer; } + entry.newer = null; + entry.older = this.newest_; + this.newest_.newer = entry; + this.newest_ = entry; + return entry.value_; }; /** - * Package-internal utility method to create SafeStyleSheet instances. - * - * @param {string} styleSheet The string to initialize the SafeStyleSheet - * object with. - * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object. - * @package + * @return {number} Count. */ -goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse = - function(styleSheet) { - return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_( - styleSheet); +ol.structs.LRUCache.prototype.getCount = function() { + return this.count_; }; /** - * Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This - * method exists only so that the compiler can dead code eliminate static - * fields (like EMPTY) when they're not accessed. - * @param {string} styleSheet - * @return {!goog.html.SafeStyleSheet} - * @private + * @return {Array.} Keys. */ -goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ = - function(styleSheet) { - this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet; - return this; +ol.structs.LRUCache.prototype.getKeys = function() { + var keys = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + keys[i++] = entry.key_; + } + goog.asserts.assert(i == this.count_, 'iterated correct number of times'); + return keys; }; /** - * A SafeStyleSheet instance corresponding to the empty string. - * @const {!goog.html.SafeStyleSheet} + * @return {Array.} Values. */ -goog.html.SafeStyleSheet.EMPTY = - goog.html.SafeStyleSheet - .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(''); +ol.structs.LRUCache.prototype.getValues = function() { + var values = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + values[i++] = entry.value_; + } + goog.asserts.assert(i == this.count_, 'iterated correct number of times'); + return values; +}; -// Copyright 2015 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Wrapper for URL and its createObjectUrl and revokeObjectUrl - * methods that are part of the HTML5 File API. + * @return {T} Last value. */ - -goog.provide('goog.fs.url'); +ol.structs.LRUCache.prototype.peekLast = function() { + goog.asserts.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.value_; +}; /** - * Creates a blob URL for a blob object. - * Throws an error if the browser does not support Object Urls. - * - * @param {!Blob} blob The object for which to create the URL. - * @return {string} The URL for the object. + * @return {string} Last key. */ -goog.fs.url.createObjectUrl = function(blob) { - return goog.fs.url.getUrlObject_().createObjectURL(blob); +ol.structs.LRUCache.prototype.peekLastKey = function() { + goog.asserts.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.key_; }; /** - * Revokes a URL created by {@link goog.fs.url.createObjectUrl}. - * Throws an error if the browser does not support Object Urls. - * - * @param {string} url The URL to revoke. + * @return {T} value Value. */ -goog.fs.url.revokeObjectUrl = function(url) { - goog.fs.url.getUrlObject_().revokeObjectURL(url); +ol.structs.LRUCache.prototype.pop = function() { + goog.asserts.assert(this.oldest_, 'oldest must not be null'); + goog.asserts.assert(this.newest_, 'newest must not be null'); + var entry = this.oldest_; + goog.asserts.assert(entry.key_ in this.entries_, + 'oldest is indexed in entries'); + delete this.entries_[entry.key_]; + if (entry.newer) { + entry.newer.older = null; + } + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer); + if (!this.oldest_) { + this.newest_ = null; + } + --this.count_; + return entry.value_; }; /** - * @typedef {{createObjectURL: (function(!Blob): string), - * revokeObjectURL: function(string): void}} - */ -goog.fs.url.UrlObject_; + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.replace = function(key, value) { + this.get(key); // update `newest_` + this.entries_[key].value_ = value; +}; /** - * Get the object that has the createObjectURL and revokeObjectURL functions for - * this browser. - * - * @return {goog.fs.url.UrlObject_} The object for this browser. - * @private + * @param {string} key Key. + * @param {T} value Value. */ -goog.fs.url.getUrlObject_ = function() { - var urlObject = goog.fs.url.findUrlObject_(); - if (urlObject != null) { - return urlObject; +ol.structs.LRUCache.prototype.set = function(key, value) { + goog.asserts.assert(!(key in {}), + 'key is not a standard property of objects (e.g. "__proto__")'); + goog.asserts.assert(!(key in this.entries_), + 'key is not used already'); + var entry = /** @type {ol.LRUCacheEntry} */ ({ + key_: key, + newer: null, + older: this.newest_, + value_: value + }); + if (!this.newest_) { + this.oldest_ = entry; } else { - throw Error('This browser doesn\'t seem to support blob URLs'); + this.newest_.newer = entry; } + this.newest_ = entry; + this.entries_[key] = entry; + ++this.count_; }; +goog.provide('ol.tilecoord'); + +goog.require('goog.asserts'); +goog.require('ol.extent'); + /** - * Finds the object that has the createObjectURL and revokeObjectURL functions - * for this browser. - * - * @return {?goog.fs.url.UrlObject_} The object for this browser or null if the - * browser does not support Object Urls. - * @suppress {unnecessaryCasts} Depending on how the code is compiled, casting - * goog.global to UrlObject_ may result in unnecessary cast warning. - * However, the cast cannot be removed because with different set of - * compiler flags, the cast is indeed necessary. As such, silencing it. - * @private + * @enum {number} */ -goog.fs.url.findUrlObject_ = function() { - // This is what the spec says to do - // http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL - if (goog.isDef(goog.global.URL) && - goog.isDef(goog.global.URL.createObjectURL)) { - return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL); - // This is what Chrome does (as of 10.0.648.6 dev) - } else if ( - goog.isDef(goog.global.webkitURL) && - goog.isDef(goog.global.webkitURL.createObjectURL)) { - return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL); - // This is what the spec used to say to do - } else if (goog.isDef(goog.global.createObjectURL)) { - return /** @type {goog.fs.url.UrlObject_} */ (goog.global); - } else { - return null; - } +ol.QuadKeyCharCode = { + ZERO: '0'.charCodeAt(0), + ONE: '1'.charCodeAt(0), + TWO: '2'.charCodeAt(0), + THREE: '3'.charCodeAt(0) }; /** - * Checks whether this browser supports Object Urls. If not, calls to - * createObjectUrl and revokeObjectUrl will result in an error. - * - * @return {boolean} True if this browser supports Object Urls. + * @param {string} str String that follows pattern “z/x/y” where x, y and z are + * numbers. + * @return {ol.TileCoord} Tile coord. */ -goog.fs.url.browserSupportsObjectUrls = function() { - return goog.fs.url.findUrlObject_() != null; +ol.tilecoord.createFromString = function(str) { + var v = str.split('/'); + goog.asserts.assert(v.length === 3, + 'must provide a string in "z/x/y" format, got "%s"', str); + return v.map(function(e) { + return parseInt(e, 10); + }); }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utility functions for supporting Bidi issues. + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. + * @return {ol.TileCoord} Tile coordinate. */ +ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { + if (opt_tileCoord !== undefined) { + opt_tileCoord[0] = z; + opt_tileCoord[1] = x; + opt_tileCoord[2] = y; + return opt_tileCoord; + } else { + return [z, x, y]; + } +}; /** - * Namespace for bidi supporting functions. + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. */ -goog.provide('goog.i18n.bidi'); -goog.provide('goog.i18n.bidi.Dir'); -goog.provide('goog.i18n.bidi.DirectionalString'); -goog.provide('goog.i18n.bidi.Format'); +ol.tilecoord.getKeyZXY = function(z, x, y) { + return z + '/' + x + '/' + y; +}; /** - * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant - * to say that the current locale is a RTL locale. This should only be used - * if you want to override the default behavior for deciding whether the - * current locale is RTL or not. - * - * {@see goog.i18n.bidi.IS_RTL} + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {number} Hash. */ -goog.define('goog.i18n.bidi.FORCE_RTL', false); +ol.tilecoord.hash = function(tileCoord) { + return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +}; /** - * Constant that defines whether or not the current locale is a RTL locale. - * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default - * to check that {@link goog.LOCALE} is one of a few major RTL locales. - * - *

This is designed to be a maximally efficient compile-time constant. For - * example, for the default goog.LOCALE, compiling - * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It - * is this design consideration that limits the implementation to only - * supporting a few major RTL locales, as opposed to the broader repertoire of - * something like goog.i18n.bidi.isRtlLanguage. - * - *

Since this constant refers to the directionality of the locale, it is up - * to the caller to determine if this constant should also be used for the - * direction of the UI. - * - * {@see goog.LOCALE} - * - * @type {boolean} - * - * TODO(user): write a test that checks that this is a compile-time constant. + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {string} Quad key. */ -goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL || - ((goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'he' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') && - (goog.LOCALE.length == 2 || goog.LOCALE.substring(2, 3) == '-' || - goog.LOCALE.substring(2, 3) == '_')) || - (goog.LOCALE.length >= 3 && - goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' && - (goog.LOCALE.length == 3 || goog.LOCALE.substring(3, 4) == '-' || - goog.LOCALE.substring(3, 4) == '_')); +ol.tilecoord.quadKey = function(tileCoord) { + var z = tileCoord[0]; + var digits = new Array(z); + var mask = 1 << (z - 1); + var i, charCode; + for (i = 0; i < z; ++i) { + charCode = ol.QuadKeyCharCode.ZERO; + if (tileCoord[1] & mask) { + charCode += 1; + } + if (tileCoord[2] & mask) { + charCode += 2; + } + digits[i] = String.fromCharCode(charCode); + mask >>= 1; + } + return digits.join(''); +}; /** - * Unicode formatting characters and directionality string constants. - * @enum {string} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCoord} Tile coordinate. */ -goog.i18n.bidi.Format = { - /** Unicode "Left-To-Right Embedding" (LRE) character. */ - LRE: '\u202A', - /** Unicode "Right-To-Left Embedding" (RLE) character. */ - RLE: '\u202B', - /** Unicode "Pop Directional Formatting" (PDF) character. */ - PDF: '\u202C', - /** Unicode "Left-To-Right Mark" (LRM) character. */ - LRM: '\u200E', - /** Unicode "Right-To-Left Mark" (RLM) character. */ - RLM: '\u200F' +ol.tilecoord.wrapX = function(tileCoord, tileGrid, projection) { + var z = tileCoord[0]; + var center = tileGrid.getTileCoordCenter(tileCoord); + var projectionExtent = ol.tilegrid.extentFromProjection(projection); + if (!ol.extent.containsCoordinate(projectionExtent, center)) { + var worldWidth = ol.extent.getWidth(projectionExtent); + var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); + center[0] += worldWidth * worldsAway; + return tileGrid.getTileCoordForCoordAndZ(center, z); + } else { + return tileCoord; + } }; /** - * Directionality enum. - * @enum {number} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {boolean} Tile coordinate is within extent and zoom level range. */ -goog.i18n.bidi.Dir = { - /** - * Left-to-right. - */ - LTR: 1, - - /** - * Right-to-left. - */ - RTL: -1, +ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) { + var z = tileCoord[0]; + var x = tileCoord[1]; + var y = tileCoord[2]; - /** - * Neither left-to-right nor right-to-left. - */ - NEUTRAL: 0 + if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { + return false; + } + var extent = tileGrid.getExtent(); + var tileRange; + if (!extent) { + tileRange = tileGrid.getFullTileRange(z); + } else { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + } + if (!tileRange) { + return true; + } else { + return tileRange.containsXY(x, y); + } }; +goog.provide('ol.TileCache'); -/** - * 'right' string constant. - * @type {string} - */ -goog.i18n.bidi.RIGHT = 'right'; +goog.require('ol.TileRange'); +goog.require('ol.structs.LRUCache'); +goog.require('ol.tilecoord'); /** - * 'left' string constant. - * @type {string} + * @constructor + * @extends {ol.structs.LRUCache.} + * @param {number=} opt_highWaterMark High water mark. + * @struct */ -goog.i18n.bidi.LEFT = 'left'; +ol.TileCache = function(opt_highWaterMark) { + ol.structs.LRUCache.call(this); -/** - * 'left' if locale is RTL, 'right' if not. - * @type {string} - */ -goog.i18n.bidi.I18N_RIGHT = - goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : goog.i18n.bidi.RIGHT; + /** + * @private + * @type {number} + */ + this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048; + +}; +ol.inherits(ol.TileCache, ol.structs.LRUCache); /** - * 'right' if locale is RTL, 'left' if not. - * @type {string} + * @return {boolean} Can expire cache. */ -goog.i18n.bidi.I18N_LEFT = - goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT; - - -/** - * Convert a directionality given in various formats to a goog.i18n.bidi.Dir - * constant. Useful for interaction with different standards of directionality - * representation. - * - * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given - * in one of the following formats: - * 1. A goog.i18n.bidi.Dir constant. - * 2. A number (positive = LTR, negative = RTL, 0 = neutral). - * 3. A boolean (true = RTL, false = LTR). - * 4. A null for unknown directionality. - * @param {boolean=} opt_noNeutral Whether a givenDir of zero or - * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in - * order to preserve legacy behavior. - * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the - * given directionality. If given null, returns null (i.e. unknown). - */ -goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { - if (typeof givenDir == 'number') { - // This includes the non-null goog.i18n.bidi.Dir case. - return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : givenDir < 0 ? - goog.i18n.bidi.Dir.RTL : - opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; - } else if (givenDir == null) { - return null; - } else { - // Must be typeof givenDir == 'boolean'. - return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; - } +ol.TileCache.prototype.canExpireCache = function() { + return this.getCount() > this.highWaterMark_; }; /** - * A practical pattern to identify strong LTR characters. This pattern is not - * theoretically correct according to the Unicode standard. It is simplified for - * performance and small code size. - * @type {string} - * @private + * @param {Object.} usedTiles Used tiles. */ -goog.i18n.bidi.ltrChars_ = - 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + - '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; +ol.TileCache.prototype.expireCache = function(usedTiles) { + var tile, zKey; + while (this.canExpireCache()) { + tile = this.peekLast(); + zKey = tile.tileCoord[0].toString(); + if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { + break; + } else { + this.pop().dispose(); + } + } +}; /** - * A practical pattern to identify strong RTL character. This pattern is not - * theoretically correct according to the Unicode standard. It is simplified - * for performance and small code size. - * @type {string} - * @private + * Remove a tile range from the cache, e.g. to invalidate tiles. + * @param {ol.TileRange} tileRange The tile range to prune. */ -goog.i18n.bidi.rtlChars_ = - '\u0591-\u06EF\u06FA-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; +ol.TileCache.prototype.pruneTileRange = function(tileRange) { + var i = this.getCount(), + key; + while (i--) { + key = this.peekLastKey(); + if (tileRange.contains(ol.tilecoord.createFromString(key))) { + this.pop().dispose(); + } else { + this.get(key); + } + } +}; +goog.provide('ol.Tile'); +goog.provide('ol.TileState'); -/** - * Simplified regular expression for an HTML tag (opening or closing) or an HTML - * escape. We might want to skip over such expressions when estimating the text - * directionality. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g; +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); /** - * Returns the input text with spaces instead of HTML tags or HTML escapes, if - * opt_isStripNeeded is true. Else returns the input as is. - * Useful for text directionality estimation. - * Note: the function should not be used in other contexts; it is not 100% - * correct, but rather a good-enough implementation for directionality - * estimation purposes. - * @param {string} str The given string. - * @param {boolean=} opt_isStripNeeded Whether to perform the stripping. - * Default: false (to retain consistency with calling functions). - * @return {string} The given string cleaned of HTML tags / escapes. - * @private + * @enum {number} */ -goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) { - return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : str; +ol.TileState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3, + EMPTY: 4, + ABORT: 5 }; /** - * Regular expression to check for RTL characters. - * @type {RegExp} - * @private + * @classdesc + * Base class for tiles. + * + * @constructor + * @extends {ol.events.EventTarget} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileState} state State. */ -goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.Tile = function(tileCoord, state) { + ol.events.EventTarget.call(this); -/** - * Regular expression to check for LTR characters. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']'); + /** + * @type {ol.TileCoord} + */ + this.tileCoord = tileCoord; + /** + * @protected + * @type {ol.TileState} + */ + this.state = state; -/** - * Test whether the given string has any RTL characters in it. - * @param {string} str The given string that need to be tested. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether the string contains RTL characters. - */ -goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlCharReg_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); -}; + /** + * An "interim" tile for this tile. The interim tile may be used while this + * one is loading, for "smooth" transitions when changing params/dimensions + * on the source. + * @type {ol.Tile} + */ + this.interimTile = null; + /** + * A key assigned to the tile. This is used by the tile source to determine + * if this tile can effectively be used, or if a new tile should be created + * and this one be used as an interim tile for this new tile. + * @type {string} + */ + this.key = ''; -/** - * Test whether the given string has any RTL characters in it. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the string contains RTL characters. - * @deprecated Use hasAnyRtl. - */ -goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl; +}; +ol.inherits(ol.Tile, ol.events.EventTarget); /** - * Test whether the given string has any LTR characters in it. - * @param {string} str The given string that need to be tested. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether the string contains LTR characters. + * @protected */ -goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrCharReg_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +ol.Tile.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); }; /** - * Regular expression pattern to check if the first character in the string - * is LTR. - * @type {RegExp} - * @private + * Get the HTML image element for this tile (may be a Canvas, Image, or Video). + * @function + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. */ -goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']'); +ol.Tile.prototype.getImage = goog.abstractMethod; /** - * Regular expression pattern to check if the first character in the string - * is RTL. - * @type {RegExp} - * @private + * @return {string} Key. */ -goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.Tile.prototype.getKey = function() { + return goog.getUid(this).toString(); +}; /** - * Check if the first character in the string is RTL or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is an RTL char. + * Get the tile coordinate for this tile. + * @return {ol.TileCoord} The tile coordinate. + * @api */ -goog.i18n.bidi.isRtlChar = function(str) { - return goog.i18n.bidi.rtlRe_.test(str); +ol.Tile.prototype.getTileCoord = function() { + return this.tileCoord; }; /** - * Check if the first character in the string is LTR or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is an LTR char. + * @return {ol.TileState} State. */ -goog.i18n.bidi.isLtrChar = function(str) { - return goog.i18n.bidi.ltrRe_.test(str); +ol.Tile.prototype.getState = function() { + return this.state; }; /** - * Check if the first character in the string is neutral or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is a neutral char. + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @api */ -goog.i18n.bidi.isNeutralChar = function(str) { - return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str); -}; +ol.Tile.prototype.load = goog.abstractMethod; +goog.provide('ol.size'); -/** - * Regular expressions to check if a piece of text is of LTR directionality - * on first character with strong directionality. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.ltrDirCheckRe_ = new RegExp( - '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']'); + +goog.require('goog.asserts'); /** - * Regular expressions to check if a piece of text is of RTL directionality - * on first character with strong directionality. - * @type {RegExp} - * @private + * Returns a buffered size. + * @param {ol.Size} size Size. + * @param {number} buffer Buffer. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The buffered size. */ -goog.i18n.bidi.rtlDirCheckRe_ = new RegExp( - '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.size.buffer = function(size, buffer, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = size[0] + 2 * buffer; + opt_size[1] = size[1] + 2 * buffer; + return opt_size; +}; /** - * Check whether the first strongly directional character (if any) is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL directionality is detected using the first - * strongly-directional character method. + * Compares sizes for equality. + * @param {ol.Size} a Size. + * @param {ol.Size} b Size. + * @return {boolean} Equals. */ -goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +ol.size.equals = function(a, b) { + return a[0] == b[0] && a[1] == b[1]; }; /** - * Check whether the first strongly directional character (if any) is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL directionality is detected using the first - * strongly-directional character method. - * @deprecated Use startsWithRtl. + * Determines if a size has a positive area. + * @param {ol.Size} size The size to test. + * @return {boolean} The size has a positive area. */ -goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl; +ol.size.hasArea = function(size) { + return size[0] > 0 && size[1] > 0; +}; /** - * Check whether the first strongly directional character (if any) is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR directionality is detected using the first - * strongly-directional character method. + * Returns a size scaled by a ratio. The result will be an array of integers. + * @param {ol.Size} size Size. + * @param {number} ratio Ratio. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The scaled size. */ -goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +ol.size.scale = function(size, ratio, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = (size[0] * ratio + 0.5) | 0; + opt_size[1] = (size[1] * ratio + 0.5) | 0; + return opt_size; }; /** - * Check whether the first strongly directional character (if any) is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR directionality is detected using the first - * strongly-directional character method. - * @deprecated Use startsWithLtr. + * Returns an `ol.Size` array for the passed in number (meaning: square) or + * `ol.Size` array. + * (meaning: non-square), + * @param {number|ol.Size} size Width and height. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} Size. + * @api stable */ -goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr; +ol.size.toSize = function(size, opt_size) { + if (Array.isArray(size)) { + return size; + } else { + goog.asserts.assert(goog.isNumber(size)); + if (opt_size === undefined) { + opt_size = [size, size]; + } else { + opt_size[0] = size; + opt_size[1] = size; + } + return opt_size; + } +}; +goog.provide('ol.source.Source'); +goog.provide('ol.source.State'); -/** - * Regular expression to check if a string looks like something that must - * always be LTR even in RTL text, e.g. a URL. When estimating the - * directionality of text containing these, we treat these as weakly LTR, - * like numbers. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/; +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.Object'); +goog.require('ol.proj'); /** - * Check whether the input string either contains no strongly directional - * characters or looks like a url. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether neutral directionality is detected. + * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. + * @enum {string} */ -goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) { - str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml); - return goog.i18n.bidi.isRequiredLtrRe_.test(str) || - !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str); +ol.source.State = { + UNDEFINED: 'undefined', + LOADING: 'loading', + READY: 'ready', + ERROR: 'error' }; /** - * Regular expressions to check if the last strongly-directional character in a - * piece of text is LTR. - * @type {RegExp} - * @private + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for {@link ol.layer.Layer} sources. + * + * A generic `change` event is triggered when the state of the source changes. + * + * @constructor + * @extends {ol.Object} + * @param {ol.SourceSourceOptions} options Source options. + * @api stable */ -goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp( - '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$'); +ol.source.Source = function(options) { + ol.Object.call(this); -/** - * Regular expressions to check if the last strongly-directional character in a - * piece of text is RTL. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp( - '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$'); + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.get(options.projection); + /** + * @private + * @type {Array.} + */ + this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions); -/** - * Check if the exit directionality a piece of text is LTR, i.e. if the last - * strongly-directional character in the string is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR exit directionality was detected. - */ -goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrExitDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); -}; + /** + * @private + * @type {string|olx.LogoOptions|undefined} + */ + this.logo_ = options.logo; + /** + * @private + * @type {ol.source.State} + */ + this.state_ = options.state !== undefined ? + options.state : ol.source.State.READY; -/** - * Check if the exit directionality a piece of text is LTR, i.e. if the last - * strongly-directional character in the string is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR exit directionality was detected. - * @deprecated Use endsWithLtr. - */ -goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr; + /** + * @private + * @type {boolean} + */ + this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false; +}; +ol.inherits(ol.source.Source, ol.Object); /** - * Check if the exit directionality a piece of text is RTL, i.e. if the last - * strongly-directional character in the string is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL exit directionality was detected. + * Turns various ways of defining an attribution to an array of `ol.Attributions`. + * + * @param {ol.AttributionLike|undefined} + * attributionLike The attributions as string, array of strings, + * `ol.Attribution`, array of `ol.Attribution` or undefined. + * @return {Array.} The array of `ol.Attribution` or null if + * `undefined` was given. */ -goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlExitDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +ol.source.Source.toAttributionsArray_ = function(attributionLike) { + if (typeof attributionLike === 'string') { + return [new ol.Attribution({html: attributionLike})]; + } else if (attributionLike instanceof ol.Attribution) { + return [attributionLike]; + } else if (Array.isArray(attributionLike)) { + var len = attributionLike.length; + var attributions = new Array(len); + for (var i = 0; i < len; i++) { + var item = attributionLike[i]; + if (typeof item === 'string') { + attributions[i] = new ol.Attribution({html: item}); + } else { + attributions[i] = item; + } + } + return attributions; + } else { + return null; + } }; /** - * Check if the exit directionality a piece of text is RTL, i.e. if the last - * strongly-directional character in the string is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL exit directionality was detected. - * @deprecated Use endsWithRtl. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {Object.} skippedFeatureUids Skipped feature uids. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. + * @return {T|undefined} Callback result. + * @template T */ -goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl; +ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction; /** - * A regular expression for matching right-to-left language codes. - * See {@link #isRtlLanguage} for the design. - * @type {RegExp} - * @private + * Get the attributions of the source. + * @return {Array.} Attributions. + * @api stable */ -goog.i18n.bidi.rtlLocalesRe_ = new RegExp( - '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' + - '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' + - '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)', - 'i'); +ol.source.Source.prototype.getAttributions = function() { + return this.attributions_; +}; /** - * Check if a BCP 47 / III language code indicates an RTL language, i.e. either: - * - a language code explicitly specifying one of the right-to-left scripts, - * e.g. "az-Arab", or

- * - a language code specifying one of the languages normally written in a - * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying - * Latin or Cyrillic script (which are the usual LTR alternatives).

- * The list of right-to-left scripts appears in the 100-199 range in - * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and - * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and - * Tifinagh, which also have significant modern usage. The rest (Syriac, - * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage - * and are not recognized to save on code size. - * The languages usually written in a right-to-left script are taken as those - * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in - * http://www.iana.org/assignments/language-subtag-registry, - * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug). - * Other subtags of the language code, e.g. regions like EG (Egypt), are - * ignored. - * @param {string} lang BCP 47 (a.k.a III) language code. - * @return {boolean} Whether the language code is an RTL language. + * Get the logo of the source. + * @return {string|olx.LogoOptions|undefined} Logo. + * @api stable */ -goog.i18n.bidi.isRtlLanguage = function(lang) { - return goog.i18n.bidi.rtlLocalesRe_.test(lang); +ol.source.Source.prototype.getLogo = function() { + return this.logo_; }; /** - * Regular expression for bracket guard replacement in text. - * @type {RegExp} - * @private + * Get the projection of the source. + * @return {ol.proj.Projection} Projection. + * @api */ -goog.i18n.bidi.bracketGuardTextRe_ = - /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g; +ol.source.Source.prototype.getProjection = function() { + return this.projection_; +}; /** - * Apply bracket guard using LRM and RLM. This is to address the problem of - * messy bracket display frequently happens in RTL layout. - * This function works for plain text, not for HTML. In HTML, the opening - * bracket might be in a different context than the closing bracket (such as - * an attribute value). - * @param {string} s The string that need to be processed. - * @param {boolean=} opt_isRtlContext specifies default direction (usually - * direction of the UI). - * @return {string} The processed string, with all bracket guarded. + * @return {Array.|undefined} Resolutions. */ -goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) { - var useRtl = opt_isRtlContext === undefined ? goog.i18n.bidi.hasAnyRtl(s) : - opt_isRtlContext; - var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM; - return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark); -}; +ol.source.Source.prototype.getResolutions = goog.abstractMethod; /** - * Enforce the html snippet in RTL directionality regardless overall context. - * If the html piece was enclosed by tag, dir will be applied to existing - * tag, otherwise a span tag will be added as wrapper. For this reason, if - * html snippet start with with tag, this tag must enclose the whole piece. If - * the tag already has a dir specified, this new one will override existing - * one in behavior (tested on FF and IE). - * @param {string} html The string that need to be processed. - * @return {string} The processed string, with directionality enforced to RTL. + * Get the state of the source, see {@link ol.source.State} for possible states. + * @return {ol.source.State} State. + * @api */ -goog.i18n.bidi.enforceRtlInHtml = function(html) { - if (html.charAt(0) == '<') { - return html.replace(/<\w+/, '$& dir=rtl'); - } - // '\n' is important for FF so that it won't incorrectly merge span groups - return '\n' + html + ''; +ol.source.Source.prototype.getState = function() { + return this.state_; }; /** - * Enforce RTL on both end of the given text piece using unicode BiDi formatting - * characters RLE and PDF. - * @param {string} text The piece of text that need to be wrapped. - * @return {string} The wrapped string after process. + * @return {boolean|undefined} Wrap X. */ -goog.i18n.bidi.enforceRtlInText = function(text) { - return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF; +ol.source.Source.prototype.getWrapX = function() { + return this.wrapX_; }; /** - * Enforce the html snippet in RTL directionality regardless overall context. - * If the html piece was enclosed by tag, dir will be applied to existing - * tag, otherwise a span tag will be added as wrapper. For this reason, if - * html snippet start with with tag, this tag must enclose the whole piece. If - * the tag already has a dir specified, this new one will override existing - * one in behavior (tested on FF and IE). - * @param {string} html The string that need to be processed. - * @return {string} The processed string, with directionality enforced to RTL. + * Refreshes the source and finally dispatches a 'change' event. + * @api */ -goog.i18n.bidi.enforceLtrInHtml = function(html) { - if (html.charAt(0) == '<') { - return html.replace(/<\w+/, '$& dir=ltr'); - } - // '\n' is important for FF so that it won't incorrectly merge span groups - return '\n' + html + ''; +ol.source.Source.prototype.refresh = function() { + this.changed(); }; /** - * Enforce LTR on both end of the given text piece using unicode BiDi formatting - * characters LRE and PDF. - * @param {string} text The piece of text that need to be wrapped. - * @return {string} The wrapped string after process. + * Set the attributions of the source. + * @param {ol.AttributionLike|undefined} attributions Attributions. + * Can be passed as `string`, `Array`, `{@link ol.Attribution}`, + * `Array<{@link ol.Attribution}>` or `undefined`. + * @api */ -goog.i18n.bidi.enforceLtrInText = function(text) { - return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF; +ol.source.Source.prototype.setAttributions = function(attributions) { + this.attributions_ = ol.source.Source.toAttributionsArray_(attributions); + this.changed(); }; /** - * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;" - * @type {RegExp} - * @private + * Set the logo of the source. + * @param {string|olx.LogoOptions|undefined} logo Logo. */ -goog.i18n.bidi.dimensionsRe_ = - /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g; +ol.source.Source.prototype.setLogo = function(logo) { + this.logo_ = logo; +}; /** - * Regular expression for left. - * @type {RegExp} - * @private + * Set the state of the source. + * @param {ol.source.State} state State. + * @protected */ -goog.i18n.bidi.leftRe_ = /left/gi; +ol.source.Source.prototype.setState = function(state) { + this.state_ = state; + this.changed(); +}; +goog.provide('ol.tilegrid.TileGrid'); -/** - * Regular expression for right. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.rightRe_ = /right/gi; +goog.require('goog.asserts'); +goog.require('ol'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.extent.Corner'); +goog.require('ol.math'); +goog.require('ol.object'); +goog.require('ol.proj'); +goog.require('ol.proj.METERS_PER_UNIT'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); /** - * Placeholder regular expression for swapping. - * @type {RegExp} - * @private + * @classdesc + * Base class for setting the grid pattern for sources accessing tiled-image + * servers. + * + * @constructor + * @param {olx.tilegrid.TileGridOptions} options Tile grid options. + * @struct + * @api stable */ -goog.i18n.bidi.tempRe_ = /%%%%/g; - +ol.tilegrid.TileGrid = function(options) { -/** - * Swap location parameters and 'left'/'right' in CSS specification. The - * processed string will be suited for RTL layout. Though this function can - * cover most cases, there are always exceptions. It is suggested to put - * those exceptions in separate group of CSS string. - * @param {string} cssStr CSS spefication string. - * @return {string} Processed CSS specification string. - */ -goog.i18n.bidi.mirrorCSS = function(cssStr) { - return cssStr - . - // reverse dimensions - replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2') - .replace(goog.i18n.bidi.leftRe_, '%%%%') - . // swap left and right - replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT) - .replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT); -}; + /** + * @protected + * @type {number} + */ + this.minZoom = options.minZoom !== undefined ? options.minZoom : 0; + /** + * @private + * @type {!Array.} + */ + this.resolutions_ = options.resolutions; + goog.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) { + return b - a; + }, true), 'resolutions must be sorted in descending order'); -/** - * Regular expression for hebrew double quote substitution, finding quote - * directly after hebrew characters. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g; + /** + * @protected + * @type {number} + */ + this.maxZoom = this.resolutions_.length - 1; + /** + * @private + * @type {ol.Coordinate} + */ + this.origin_ = options.origin !== undefined ? options.origin : null; -/** - * Regular expression for hebrew single quote substitution, finding quote - * directly after hebrew characters. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g; + /** + * @private + * @type {Array.} + */ + this.origins_ = null; + if (options.origins !== undefined) { + this.origins_ = options.origins; + goog.asserts.assert(this.origins_.length == this.resolutions_.length, + 'number of origins and resolutions must be equal'); + } + var extent = options.extent; -/** - * Replace the double and single quote directly after a Hebrew character with - * GERESH and GERSHAYIM. In such case, most likely that's user intention. - * @param {string} str String that need to be processed. - * @return {string} Processed string with double/single quote replaced. - */ -goog.i18n.bidi.normalizeHebrewQuote = function(str) { - return str.replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4') - .replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3'); -}; + if (extent !== undefined && + !this.origin_ && !this.origins_) { + this.origin_ = ol.extent.getTopLeft(extent); + } + goog.asserts.assert( + (!this.origin_ && this.origins_) || + (this.origin_ && !this.origins_), + 'either origin or origins must be configured, never both'); -/** - * Regular expression to split a string into "words" for directionality - * estimation based on relative word counts. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.wordSeparatorRe_ = /\s+/; + /** + * @private + * @type {Array.} + */ + this.tileSizes_ = null; + if (options.tileSizes !== undefined) { + this.tileSizes_ = options.tileSizes; + goog.asserts.assert(this.tileSizes_.length == this.resolutions_.length, + 'number of tileSizes and resolutions must be equal'); + } + /** + * @private + * @type {number|ol.Size} + */ + this.tileSize_ = options.tileSize !== undefined ? + options.tileSize : + !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null; + goog.asserts.assert( + (!this.tileSize_ && this.tileSizes_) || + (this.tileSize_ && !this.tileSizes_), + 'either tileSize or tileSizes must be configured, never both'); -/** - * Regular expression to check if a string contains any numerals. Used to - * differentiate between completely neutral strings and those containing - * numbers, which are weakly LTR. - * - * Native Arabic digits (\u0660 - \u0669) are not included because although they - * do flow left-to-right inside a number, this is the case even if the overall - * directionality is RTL, and a mathematical expression using these digits is - * supposed to flow right-to-left overall, including unary plus and minus - * appearing to the right of a number, and this does depend on the overall - * directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the - * other hand, are included, since Farsi math (including unary plus and minus) - * does flow left-to-right. - * - * @type {RegExp} - * @private - */ -goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/; + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent !== undefined ? extent : null; -/** - * This constant controls threshold of RTL directionality. - * @type {number} - * @private - */ -goog.i18n.bidi.rtlDetectionThreshold_ = 0.40; + /** + * @private + * @type {Array.} + */ + this.fullTileRanges_ = null; + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; -/** - * Estimates the directionality of a string based on relative word counts. - * If the number of RTL words is above a certain percentage of the total number - * of strongly directional words, returns RTL. - * Otherwise, if any words are strongly or weakly LTR, returns LTR. - * Otherwise, returns UNKNOWN, which is used to mean "neutral". - * Numbers are counted as weakly LTR. - * @param {string} str The string to be checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. - */ -goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { - var rtlCount = 0; - var totalCount = 0; - var hasWeaklyLtr = false; - var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml) - .split(goog.i18n.bidi.wordSeparatorRe_); - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - if (goog.i18n.bidi.startsWithRtl(token)) { - rtlCount++; - totalCount++; - } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) { - hasWeaklyLtr = true; - } else if (goog.i18n.bidi.hasAnyLtr(token)) { - totalCount++; - } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) { - hasWeaklyLtr = true; - } + if (options.sizes !== undefined) { + goog.asserts.assert(options.sizes.length == this.resolutions_.length, + 'number of sizes and resolutions must be equal'); + this.fullTileRanges_ = options.sizes.map(function(size, z) { + goog.asserts.assert(size[0] !== 0, 'width must not be 0'); + goog.asserts.assert(size[1] !== 0, 'height must not be 0'); + var tileRange = new ol.TileRange( + Math.min(0, size[0]), Math.max(size[0] - 1, -1), + Math.min(0, size[1]), Math.max(size[1] - 1, -1)); + if (this.minZoom <= z && z <= this.maxZoom && extent !== undefined) { + goog.asserts.assert(tileRange.containsTileRange( + this.getTileRangeForExtentAndZ(extent, z)), + 'extent tile range must not exceed tilegrid width and height'); + } + return tileRange; + }, this); + } else if (extent) { + this.calculateTileRanges_(extent); } - return totalCount == 0 ? - (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : - (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ? - goog.i18n.bidi.Dir.RTL : - goog.i18n.bidi.Dir.LTR); }; /** - * Check the directionality of a piece of text, return true if the piece of - * text should be laid out in RTL direction. - * @param {string} str The piece of text that need to be detected. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether this piece of text should be laid out in RTL. + * @private + * @type {ol.TileCoord} */ -goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) { - return goog.i18n.bidi.estimateDirection(str, opt_isHtml) == - goog.i18n.bidi.Dir.RTL; -}; +ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; /** - * Sets text input element's directionality and text alignment based on a - * given directionality. Does nothing if the given directionality is unknown or - * neutral. - * @param {Element} element Input field element to set directionality to. - * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality, - * given in one of the following formats: - * 1. A goog.i18n.bidi.Dir constant. - * 2. A number (positive = LRT, negative = RTL, 0 = neutral). - * 3. A boolean (true = RTL, false = LTR). - * 4. A null for unknown directionality. + * Call a function with each tile coordinate for a given extent and zoom level. + * + * @param {ol.Extent} extent Extent. + * @param {number} zoom Zoom level. + * @param {function(ol.TileCoord)} callback Function called with each tile coordinate. + * @api */ -goog.i18n.bidi.setElementDirAndAlign = function(element, dir) { - if (element) { - dir = goog.i18n.bidi.toDir(dir); - if (dir) { - element.style.textAlign = dir == goog.i18n.bidi.Dir.RTL ? - goog.i18n.bidi.RIGHT : - goog.i18n.bidi.LEFT; - element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; +ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) { + var tileRange = this.getTileRangeForExtentAndZ(extent, zoom); + for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) { + for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) { + callback([zoom, i, j]); } } }; /** - * Sets element dir based on estimated directionality of the given text. - * @param {!Element} element - * @param {string} text + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. + * @param {T=} opt_this The object to use as `this` in `callback`. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {boolean} Callback succeeded. + * @template T */ -goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) { - switch (goog.i18n.bidi.estimateDirection(text)) { - case (goog.i18n.bidi.Dir.LTR): - element.dir = 'ltr'; - break; - case (goog.i18n.bidi.Dir.RTL): - element.dir = 'rtl'; - break; - default: - // Default for no direction, inherit from document. - element.removeAttribute('dir'); +ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + var z = tileCoord[0] - 1; + while (z >= this.minZoom) { + if (callback.call(opt_this, z, + this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { + return true; + } + --z; } + return false; }; - -/** - * Strings that have an (optional) known direction. - * - * Implementations of this interface are string-like objects that carry an - * attached direction, if known. - * @interface - */ -goog.i18n.bidi.DirectionalString = function() {}; - - -/** - * Interface marker of the DirectionalString interface. - * - * This property can be used to determine at runtime whether or not an object - * implements this interface. All implementations of this interface set this - * property to {@code true}. - * @type {boolean} - */ -goog.i18n.bidi.DirectionalString.prototype - .implementsGoogI18nBidiDirectionalString; - - -/** - * Retrieves this object's known direction (if any). - * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown. - */ -goog.i18n.bidi.DirectionalString.prototype.getDirection; - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview The SafeUrl type and its builders. - * - * TODO(xtof): Link to document stating type contract. - */ - -goog.provide('goog.html.SafeUrl'); - -goog.require('goog.asserts'); -goog.require('goog.fs.url'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - - - /** - * A string that is safe to use in URL context in DOM APIs and HTML documents. - * - * A SafeUrl is a string-like object that carries the security type contract - * that its value as a string will not cause untrusted script execution - * when evaluated as a hyperlink URL in a browser. - * - * Values of this type are guaranteed to be safe to use in URL/hyperlink - * contexts, such as, assignment to URL-valued DOM properties, or - * interpolation into a HTML template in URL context (e.g., inside a href - * attribute), in the sense that the use will not result in a - * Cross-Site-Scripting vulnerability. - * - * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's - * contract does not guarantee that instances are safe to interpolate into HTML - * without appropriate escaping. - * - * Note also that this type's contract does not imply any guarantees regarding - * the resource the URL refers to. In particular, SafeUrls are not - * safe to use in a context where the referred-to resource is interpreted as - * trusted code, e.g., as the src of a script tag. - * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}), - * etc and not by invoking its constructor. The constructor intentionally - * takes no parameters and the type is immutable; hence only a default instance - * corresponding to the empty string can be obtained via constructor invocation. - * - * @see goog.html.SafeUrl#fromConstant - * @see goog.html.SafeUrl#from - * @see goog.html.SafeUrl#sanitize - * @constructor - * @final - * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * Get the extent for this tile grid, if it was configured. + * @return {ol.Extent} Extent. */ -goog.html.SafeUrl = function() { - /** - * The contained value of this SafeUrl. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; - - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeUrl#unwrap - * @const - * @private - */ - this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +ol.tilegrid.TileGrid.prototype.getExtent = function() { + return this.extent_; }; /** - * The innocuous string generated by goog.html.SafeUrl.sanitize when passed - * an unsafe URL. - * - * about:invalid is registered in - * http://www.w3.org/TR/css3-values/#about-invalid. - * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to - * contain a fragment, which is not to be considered when determining if an - * about URL is well-known. - * - * Using about:invalid seems preferable to using a fixed data URL, since - * browsers might choose to not report CSP violations on it, as legitimate - * CSS function calls to attr() can result in this URL being produced. It is - * also a standard URL which matches exactly the semantics we need: - * "The about:invalid URI references a non-existent document with a generic - * error condition. It can be used when a URI is necessary, but the default - * value shouldn't be resolveable as any type of document". - * - * @const {string} + * Get the maximum zoom level for the grid. + * @return {number} Max zoom. + * @api */ -goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez'; +ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { + return this.maxZoom; +}; /** - * @override - * @const + * Get the minimum zoom level for the grid. + * @return {number} Min zoom. + * @api */ -goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true; +ol.tilegrid.TileGrid.prototype.getMinZoom = function() { + return this.minZoom; +}; /** - * Returns this SafeUrl's value a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this - * method. If in doubt, assume that it's security relevant. In particular, note - * that goog.html functions which return a goog.html type do not guarantee that - * the returned instance is of the right type. For example: - * - *

- * var fakeSafeHtml = new String('fake');
- * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
- * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
- * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
- * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
- * // goog.html.SafeHtml.
- * 
- * - * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the - * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST - * be appropriately escaped before embedding in a HTML document. Note that the - * required escaping is context-sensitive (e.g. a different escaping is - * required for embedding a URL in a style property within a style - * attribute, as opposed to embedding in a href attribute). - * - * @see goog.html.SafeUrl#unwrap - * @override + * Get the origin for the grid at the given zoom level. + * @param {number} z Z. + * @return {ol.Coordinate} Origin. + * @api stable */ -goog.html.SafeUrl.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { + if (this.origin_) { + return this.origin_; + } else { + goog.asserts.assert(this.origins_, + 'origins cannot be null if origin is null'); + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.origins_[z]; + } }; /** - * @override - * @const + * Get the resolution for the given zoom level. + * @param {number} z Z. + * @return {number} Resolution. + * @api stable */ -goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true; +ol.tilegrid.TileGrid.prototype.getResolution = function(z) { + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.resolutions_[z]; +}; /** - * Returns this URLs directionality, which is always {@code LTR}. - * @override + * Get the list of resolutions for the tile grid. + * @return {Array.} Resolutions. + * @api stable */ -goog.html.SafeUrl.prototype.getDirection = function() { - return goog.i18n.bidi.Dir.LTR; +ol.tilegrid.TileGrid.prototype.getResolutions = function() { + return this.resolutions_; }; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeUrl, use - * {@code goog.html.SafeUrl.unwrap}. - * - * @see goog.html.SafeUrl#unwrap - * @override - */ - goog.html.SafeUrl.prototype.toString = function() { - return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + - '}'; - }; -} - - /** - * Performs a runtime check that the provided object is indeed a SafeUrl - * object, and returns its value. - * - * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the - * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST - * be appropriately escaped before embedding in a HTML document. Note that the - * required escaping is context-sensitive (e.g. a different escaping is - * required for embedding a URL in a style property within a style - * attribute, as opposed to embedding in a href attribute). - * - * @param {!goog.html.SafeUrl} safeUrl The object to extract from. - * @return {string} The SafeUrl object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeUrl.unwrap = function(safeUrl) { - // Perform additional Run-time type-checking to ensure that safeUrl is indeed - // an instance of the expected type. This provides some additional protection - // against security bugs due to application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeUrl instanceof goog.html.SafeUrl && - safeUrl.constructor === goog.html.SafeUrl && - safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_; + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) { + if (tileCoord[0] < this.maxZoom) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + return this.getTileRangeForExtentAndZ( + tileCoordExtent, tileCoord[0] + 1, opt_tileRange); } else { - goog.asserts.fail('expected object of type SafeUrl, got \'' + - safeUrl + '\' of type ' + goog.typeOf(safeUrl)); - return 'type_error:SafeUrl'; + return null; } }; /** - * Creates a SafeUrl object from a compile-time constant string. - * - * Compile-time constant strings are inherently program-controlled and hence - * trusted. - * - * @param {!goog.string.Const} url A compile-time-constant string from which to - * create a SafeUrl. - * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}. + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.Extent} Extent. */ -goog.html.SafeUrl.fromConstant = function(url) { - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( - goog.string.Const.unwrap(url)); +ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) { + var origin = this.getOrigin(z); + var resolution = this.getResolution(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; + var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; + var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; + var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; /** - * A pattern that matches Blob or data types that can have SafeUrls created - * from URL.createObjectURL(blob) or via a data: URI. Only matches image and - * video types, currently. - * @const - * @private + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. */ -goog.html.SAFE_MIME_TYPE_PATTERN_ = - /^(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm))$/i; +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) { + var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; + this.getTileCoordForXYAndResolution_( + extent[0], extent[1], resolution, false, tileCoord); + var minX = tileCoord[1]; + var minY = tileCoord[2]; + this.getTileCoordForXYAndResolution_( + extent[2], extent[3], resolution, true, tileCoord); + return ol.TileRange.createOrUpdate( + minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +}; /** - * Creates a SafeUrl wrapping a blob URL for the given {@code blob}. - * - * The blob URL is created with {@code URL.createObjectURL}. If the MIME type - * for {@code blob} is not of a known safe image or video MIME type, then the - * SafeUrl will wrap {@link #INNOCUOUS_STRING}. - * - * @see http://www.w3.org/TR/FileAPI/#url - * @param {!Blob} blob - * @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped - * as a SafeUrl. + * @param {ol.Extent} extent Extent. + * @param {number} z Z. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. */ -goog.html.SafeUrl.fromBlob = function(blob) { - var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ? - goog.fs.url.createObjectUrl(blob) : - goog.html.SafeUrl.INNOCUOUS_STRING; - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) { + var resolution = this.getResolution(z); + return this.getTileRangeForExtentAndResolution( + extent, resolution, opt_tileRange); }; /** - * Matches a base-64 data URL, with the first match group being the MIME type. - * @const - * @private + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {ol.Coordinate} Tile center. */ -goog.html.DATA_URL_PATTERN_ = /^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i; +ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + return [ + origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, + origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution + ]; +}; /** - * Creates a SafeUrl wrapping a data: URL, after validating it matches a - * known-safe image or video MIME type. + * Get the extent of a tile coordinate. * - * @param {string} dataUrl A valid base64 data URL with one of the whitelisted - * image or video MIME types. - * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING} - * wrapped as a SafeUrl if it does not pass. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Extent=} opt_extent Temporary extent object. + * @return {ol.Extent} Extent. + * @api */ -goog.html.SafeUrl.fromDataUrl = function(dataUrl) { - // There's a slight risk here that a browser sniffs the content type if it - // doesn't know the MIME type and executes HTML within the data: URL. For this - // to cause XSS it would also have to execute the HTML in the same origin - // of the page with the link. It seems unlikely that both of these will - // happen, particularly in not really old IEs. - var match = dataUrl.match(goog.html.DATA_URL_PATTERN_); - var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]); - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( - valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING); +ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; + var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; + var maxY = minY + tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; /** - * Creates a SafeUrl wrapping a tel: URL. + * Get the tile coordinate for the given map coordinate and resolution. This + * method considers that coordinates that intersect tile boundaries should be + * assigned the higher tile coordinate. * - * @param {string} telUrl A tel URL. - * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING} - * wrapped as a SafeUrl if it does not pass. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api */ -goog.html.SafeUrl.fromTelUrl = function(telUrl) { - // There's a risk that a tel: URL could immediately place a call once - // clicked, without requiring user confirmation. For that reason it is - // handled in this separate function. - if (!goog.string.caseInsensitiveStartsWith(telUrl, 'tel:')) { - telUrl = goog.html.SafeUrl.INNOCUOUS_STRING; - } - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( - telUrl); +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) { + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); }; /** - * A pattern that recognizes a commonly useful subset of URLs that satisfy - * the SafeUrl contract. - * - * This regular expression matches a subset of URLs that will not cause script - * execution if used in URL context within a HTML document. Specifically, this - * regular expression matches if (comment from here on and regex copied from - * Soy's EscapingConventions): - * (1) Either a protocol in a whitelist (http, https, mailto or ftp). - * (2) or no protocol. A protocol must be followed by a colon. The below - * allows that by allowing colons only after one of the characters [/?#]. - * A colon after a hash (#) must be in the fragment. - * Otherwise, a colon after a (?) must be in a query. - * Otherwise, a colon after a single solidus (/) must be in a path. - * Otherwise, a colon after a double solidus (//) must be in the authority - * (before port). - * - * The pattern disallows &, used in HTML entity declarations before - * one of the characters in [/?#]. This disallows HTML entities used in the - * protocol name, which should never happen, e.g. "http" for "http". - * It also disallows HTML entities in the first path part of a relative path, - * e.g. "foo<bar/baz". Our existing escaping functions should not produce - * that. More importantly, it disallows masking of a colon, - * e.g. "javascript:...". - * + * @param {number} x X. + * @param {number} y Y. + * @param {number} resolution Resolution. + * @param {boolean} reverseIntersectionPolicy Instead of letting edge + * intersections go to the higher tile coordinate, let edge intersections + * go to the lower tile coordinate. + * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. * @private - * @const {!RegExp} */ -goog.html.SAFE_URL_PATTERN_ = - /^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i; +ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( + x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { + var z = this.getZForResolution(resolution); + var scale = resolution / this.getResolution(z); + var origin = this.getOrigin(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var adjustX = reverseIntersectionPolicy ? 0.5 : 0; + var adjustY = reverseIntersectionPolicy ? 0 : 0.5; + var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); + var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY); + var tileCoordX = scale * xFromOrigin / tileSize[0]; + var tileCoordY = scale * yFromOrigin / tileSize[1]; -/** - * Creates a SafeUrl object from {@code url}. If {@code url} is a - * goog.html.SafeUrl then it is simply returned. Otherwise the input string is - * validated to match a pattern of commonly used safe URLs. - * - * {@code url} may be a URL with the http, https, mailto or ftp scheme, - * or a relative URL (i.e., a URL without a scheme; specifically, a - * scheme-relative, absolute-path-relative, or path-relative URL). - * - * @see http://url.spec.whatwg.org/#concept-relative-url - * @param {string|!goog.string.TypedString} url The URL to validate. - * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl. - */ -goog.html.SafeUrl.sanitize = function(url) { - if (url instanceof goog.html.SafeUrl) { - return url; - } else if (url.implementsGoogStringTypedString) { - url = url.getTypedStringValue(); + if (reverseIntersectionPolicy) { + tileCoordX = Math.ceil(tileCoordX) - 1; + tileCoordY = Math.ceil(tileCoordY) - 1; } else { - url = String(url); - } - if (!goog.html.SAFE_URL_PATTERN_.test(url)) { - url = goog.html.SafeUrl.INNOCUOUS_STRING; + tileCoordX = Math.floor(tileCoordX); + tileCoordY = Math.floor(tileCoordY); } - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); + + return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); }; /** - * Type marker for the SafeUrl type, used to implement additional run-time - * type checking. - * @const {!Object} - * @private + * Get a tile coordinate given a map coordinate and zoom level. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} z Zoom level. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api */ -goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) { + var resolution = this.getResolution(z); + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; /** - * Package-internal utility method to create SafeUrl instances. - * - * @param {string} url The string to initialize the SafeUrl object with. - * @return {!goog.html.SafeUrl} The initialized SafeUrl object. - * @package + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {number} Tile resolution. */ -goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function( - url) { - var safeUrl = new goog.html.SafeUrl(); - safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url; - return safeUrl; +ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { + goog.asserts.assert( + this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom, + 'z of given tilecoord is not in allowed range (%s <= %s <= %s', + this.minZoom, tileCoord[0], this.maxZoom); + return this.resolutions_[tileCoord[0]]; }; /** - * A SafeUrl corresponding to the special about:blank url. - * @const {!goog.html.SafeUrl} + * Get the tile size for a zoom level. The type of the return value matches the + * `tileSize` or `tileSizes` that the tile grid was configured with. To always + * get an `ol.Size`, run the result through `ol.size.toSize()`. + * @param {number} z Z. + * @return {number|ol.Size} Tile size. + * @api stable */ -goog.html.SafeUrl.ABOUT_BLANK = - goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( - 'about:blank'); - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview The TrustedResourceUrl type and its builders. - * - * TODO(xtof): Link to document stating type contract. - */ - -goog.provide('goog.html.TrustedResourceUrl'); - -goog.require('goog.asserts'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - +ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { + if (this.tileSize_) { + return this.tileSize_; + } else { + goog.asserts.assert(this.tileSizes_, + 'tileSizes cannot be null if tileSize is null'); + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.tileSizes_[z]; + } +}; /** - * A URL which is under application control and from which script, CSS, and - * other resources that represent executable code, can be fetched. - * - * Given that the URL can only be constructed from strings under application - * control and is used to load resources, bugs resulting in a malformed URL - * should not have a security impact and are likely to be easily detectable - * during testing. Given the wide number of non-RFC compliant URLs in use, - * stricter validation could prevent some applications from being able to use - * this type. - * - * Instances of this type must be created via the factory method, - * ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty - * string can be obtained via constructor invocation. - * - * @see goog.html.TrustedResourceUrl#fromConstant - * @constructor - * @final - * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * @param {number} z Zoom level. + * @return {ol.TileRange} Extent tile range for the specified zoom level. */ -goog.html.TrustedResourceUrl = function() { - /** - * The contained value of this TrustedResourceUrl. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = ''; - - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.TrustedResourceUrl#unwrap - * @const - * @private - */ - this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { + if (!this.fullTileRanges_) { + return null; + } else { + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.fullTileRanges_[z]; + } }; /** - * @override - * @const + * @param {number} resolution Resolution. + * @param {number=} opt_direction If 0, the nearest resolution will be used. + * If 1, the nearest lower resolution will be used. If -1, the nearest + * higher resolution will be used. Default is 0. + * @return {number} Z. + * @api */ -goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true; +ol.tilegrid.TileGrid.prototype.getZForResolution = function( + resolution, opt_direction) { + var z = ol.array.linearFindNearest(this.resolutions_, resolution, + opt_direction || 0); + return ol.math.clamp(z, this.minZoom, this.maxZoom); +}; /** - * Returns this TrustedResourceUrl's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code TrustedResourceUrl}, use - * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in - * doubt, assume that it's security relevant. In particular, note that - * goog.html functions which return a goog.html type do not guarantee that - * the returned instance is of the right type. For example: - * - *
- * var fakeSafeHtml = new String('fake');
- * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
- * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
- * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
- * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
- * // goog.html.SafeHtml.
- * 
- * - * @see goog.html.TrustedResourceUrl#unwrap - * @override + * @param {!ol.Extent} extent Extent for this tile grid. + * @private */ -goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; +ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) { + var length = this.resolutions_.length; + var fullTileRanges = new Array(length); + for (var z = this.minZoom; z < length; ++z) { + fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z); + } + this.fullTileRanges_ = fullTileRanges; }; /** - * @override - * @const + * @param {ol.proj.Projection} projection Projection. + * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection. */ -goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString = - true; +ol.tilegrid.getForProjection = function(projection) { + var tileGrid = projection.getDefaultTileGrid(); + if (!tileGrid) { + tileGrid = ol.tilegrid.createForProjection(projection); + projection.setDefaultTileGrid(tileGrid); + } + return tileGrid; +}; /** - * Returns this URLs directionality, which is always {@code LTR}. - * @override + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.TOP_LEFT). + * @return {ol.tilegrid.TileGrid} TileGrid instance. */ -goog.html.TrustedResourceUrl.prototype.getDirection = function() { - return goog.i18n.bidi.Dir.LTR; -}; +ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { + var corner = opt_corner !== undefined ? + opt_corner : ol.extent.Corner.TOP_LEFT; + var resolutions = ol.tilegrid.resolutionsFromExtent( + extent, opt_maxZoom, opt_tileSize); -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a TrustedResourceUrl, use - * {@code goog.html.TrustedResourceUrl.unwrap}. - * - * @see goog.html.TrustedResourceUrl#unwrap - * @override - */ - goog.html.TrustedResourceUrl.prototype.toString = function() { - return 'TrustedResourceUrl{' + - this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}'; - }; -} + return new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getCorner(extent, corner), + resolutions: resolutions, + tileSize: opt_tileSize + }); +}; /** - * Performs a runtime check that the provided object is indeed a - * TrustedResourceUrl object, and returns its value. - * - * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to - * extract from. - * @return {string} The trustedResourceUrl object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) { - // Perform additional Run-time type-checking to ensure that - // trustedResourceUrl is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl && - trustedResourceUrl.constructor === goog.html.TrustedResourceUrl && - trustedResourceUrl - .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.TrustedResourceUrl - .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return trustedResourceUrl - .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; - } else { - goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' + - trustedResourceUrl + '\' of type ' + goog.typeOf(trustedResourceUrl)); - return 'type_error:TrustedResourceUrl'; + * Creates a tile grid with a standard XYZ tiling scheme. + * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. + * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @api + */ +ol.tilegrid.createXYZ = function(opt_options) { + var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); + ol.object.assign(options, opt_options !== undefined ? + opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); + if (options.extent === undefined) { + options.extent = ol.proj.get('EPSG:3857').getExtent(); } + options.resolutions = ol.tilegrid.resolutionsFromExtent( + options.extent, options.maxZoom, options.tileSize); + delete options.maxZoom; + + return new ol.tilegrid.TileGrid(options); }; /** - * Creates a TrustedResourceUrl object from a compile-time constant string. - * - * Compile-time constant strings are inherently program-controlled and hence - * trusted. - * - * @param {!goog.string.Const} url A compile-time-constant string from which to - * create a TrustedResourceUrl. - * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object - * initialized to {@code url}. + * Create a resolutions array from an extent. A zoom factor of 2 is assumed. + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @return {!Array.} Resolutions array. */ -goog.html.TrustedResourceUrl.fromConstant = function(url) { - return goog.html.TrustedResourceUrl - .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse( - goog.string.Const.unwrap(url)); -}; +ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) { + var maxZoom = opt_maxZoom !== undefined ? + opt_maxZoom : ol.DEFAULT_MAX_ZOOM; + var height = ol.extent.getHeight(extent); + var width = ol.extent.getWidth(extent); -/** - * Creates a TrustedResourceUrl object from a compile-time constant strings. - * - * Compile-time constant strings are inherently program-controlled and hence - * trusted. - * - * @param {!Array} parts Compile-time-constant strings from - * which to create a TrustedResourceUrl. - * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object - * initialized to concatenation of {@code parts}. - */ -goog.html.TrustedResourceUrl.fromConstants = function(parts) { - var unwrapped = ''; - for (var i = 0; i < parts.length; i++) { - unwrapped += goog.string.Const.unwrap(parts[i]); + var tileSize = ol.size.toSize(opt_tileSize !== undefined ? + opt_tileSize : ol.DEFAULT_TILE_SIZE); + var maxResolution = Math.max( + width / tileSize[0], height / tileSize[1]); + + var length = maxZoom + 1; + var resolutions = new Array(length); + for (var z = 0; z < length; ++z) { + resolutions[z] = maxResolution / Math.pow(2, z); } - return goog.html.TrustedResourceUrl - .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(unwrapped); + return resolutions; }; /** - * Type marker for the TrustedResourceUrl type, used to implement additional - * run-time type checking. - * @const {!Object} - * @private - */ -goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; - - -/** - * Package-internal utility method to create TrustedResourceUrl instances. - * - * @param {string} url The string to initialize the TrustedResourceUrl object - * with. - * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl - * object. - * @package + * @param {ol.ProjectionLike} projection Projection. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.BOTTOM_LEFT). + * @return {ol.tilegrid.TileGrid} TileGrid instance. */ -goog.html.TrustedResourceUrl - .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) { - var trustedResourceUrl = new goog.html.TrustedResourceUrl(); - trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = - url; - return trustedResourceUrl; +ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) { + var extent = ol.tilegrid.extentFromProjection(projection); + return ol.tilegrid.createForExtent( + extent, opt_maxZoom, opt_tileSize, opt_corner); }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - /** - * @fileoverview The SafeHtml type and its builders. - * - * TODO(xtof): Link to document stating type contract. + * Generate a tile grid extent from a projection. If the projection has an + * extent, it is used. If not, a global extent is assumed. + * @param {ol.ProjectionLike} projection Projection. + * @return {ol.Extent} Extent. */ +ol.tilegrid.extentFromProjection = function(projection) { + projection = ol.proj.get(projection); + var extent = projection.getExtent(); + if (!extent) { + var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit(); + extent = ol.extent.createOrUpdate(-half, -half, half, half); + } + return extent; +}; -goog.provide('goog.html.SafeHtml'); +goog.provide('ol.source.Tile'); +goog.provide('ol.source.TileEvent'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.tags'); -goog.require('goog.html.SafeStyle'); -goog.require('goog.html.SafeStyleSheet'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.labs.userAgent.browser'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - +goog.require('ol.events.Event'); +goog.require('ol'); +goog.require('ol.TileCache'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.Source'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid.TileGrid'); /** - * A string that is safe to use in HTML context in DOM APIs and HTML documents. - * - * A SafeHtml is a string-like object that carries the security type contract - * that its value as a string will not cause untrusted script execution when - * evaluated as HTML in a browser. - * - * Values of this type are guaranteed to be safe to use in HTML contexts, - * such as, assignment to the innerHTML DOM property, or interpolation into - * a HTML template in HTML PC_DATA context, in the sense that the use will not - * result in a Cross-Site-Scripting vulnerability. - * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}), - * etc and not by invoking its constructor. The constructor intentionally - * takes no parameters and the type is immutable; hence only a default instance - * corresponding to the empty string can be obtained via constructor invocation. + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing images divided into a tile grid. * - * @see goog.html.SafeHtml#create - * @see goog.html.SafeHtml#htmlEscape * @constructor - * @final - * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * @extends {ol.source.Source} + * @param {ol.SourceTileOptions} options Tile source options. + * @api */ -goog.html.SafeHtml = function() { +ol.source.Tile = function(options) { + + ol.source.Source.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state, + wrapX: options.wrapX + }); + /** - * The contained value of this SafeHtml. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} + * @private + * @type {boolean} */ - this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; + this.opaque_ = options.opaque !== undefined ? options.opaque : false; /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeHtml#unwrap - * @const * @private + * @type {number} */ - this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + this.tilePixelRatio_ = options.tilePixelRatio !== undefined ? + options.tilePixelRatio : 1; /** - * This SafeHtml's directionality, or null if unknown. - * @private {?goog.i18n.bidi.Dir} + * @protected + * @type {ol.tilegrid.TileGrid} */ - this.dir_ = null; -}; + this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null; + /** + * @protected + * @type {ol.TileCache} + */ + this.tileCache = new ol.TileCache(options.cacheSize); -/** - * @override - * @const - */ -goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true; + /** + * @protected + * @type {ol.Size} + */ + this.tmpSize = [0, 0]; + /** + * @private + * @type {string} + */ + this.key_ = ''; -/** @override */ -goog.html.SafeHtml.prototype.getDirection = function() { - return this.dir_; }; +ol.inherits(ol.source.Tile, ol.source.Source); /** - * @override - * @const - */ -goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true; - - -/** - * Returns this SafeHtml's value as string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of - * this method. If in doubt, assume that it's security relevant. In particular, - * note that goog.html functions which return a goog.html type do not guarantee - * that the returned instance is of the right type. For example: - * - *
- * var fakeSafeHtml = new String('fake');
- * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
- * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
- * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
- * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
- * // instanceof goog.html.SafeHtml.
- * 
- * - * @see goog.html.SafeHtml#unwrap - * @override + * @return {boolean} Can expire cache. */ -goog.html.SafeHtml.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +ol.source.Tile.prototype.canExpireCache = function() { + return this.tileCache.canExpireCache(); }; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeHtml, use - * {@code goog.html.SafeHtml.unwrap}. - * - * @see goog.html.SafeHtml#unwrap - * @override - */ - goog.html.SafeHtml.prototype.toString = function() { - return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + - '}'; - }; -} - - /** - * Performs a runtime check that the provided object is indeed a SafeHtml - * object, and returns its value. - * @param {!goog.html.SafeHtml} safeHtml The object to extract from. - * @return {string} The SafeHtml object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeHtml.unwrap = function(safeHtml) { - // Perform additional run-time type-checking to ensure that safeHtml is indeed - // an instance of the expected type. This provides some additional protection - // against security bugs due to application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeHtml instanceof goog.html.SafeHtml && - safeHtml.constructor === goog.html.SafeHtml && - safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_; - } else { - goog.asserts.fail('expected object of type SafeHtml, got \'' + - safeHtml + '\' of type ' + goog.typeOf(safeHtml)); - return 'type_error:SafeHtml'; + * @param {ol.proj.Projection} projection Projection. + * @param {Object.} usedTiles Used tiles. + */ +ol.source.Tile.prototype.expireCache = function(projection, usedTiles) { + var tileCache = this.getTileCacheForProjection(projection); + if (tileCache) { + tileCache.expireCache(usedTiles); } }; /** - * Shorthand for union of types that can sensibly be converted to strings - * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString). - * @private - * @typedef {string|number|boolean|!goog.string.TypedString| - * !goog.i18n.bidi.DirectionalString} - */ -goog.html.SafeHtml.TextOrHtml_; - - -/** - * Returns HTML-escaped text as a SafeHtml object. - * - * If text is of a type that implements - * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new - * {@code SafeHtml} object is set to {@code text}'s directionality, if known. - * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e., - * {@code null}). - * - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + * @param {ol.proj.Projection} projection Projection. + * @param {number} z Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @param {function(ol.Tile):(boolean|undefined)} callback Called with each + * loaded tile. If the callback returns `false`, the tile will not be + * considered loaded. + * @return {boolean} The tile range is fully covered with loaded tiles. */ -goog.html.SafeHtml.htmlEscape = function(textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; - } - var dir = null; - if (textOrHtml.implementsGoogI18nBidiDirectionalString) { - dir = textOrHtml.getDirection(); +ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) { + var tileCache = this.getTileCacheForProjection(projection); + if (!tileCache) { + return false; } - var textAsString; - if (textOrHtml.implementsGoogStringTypedString) { - textAsString = textOrHtml.getTypedStringValue(); - } else { - textAsString = String(textOrHtml); + + var covered = true; + var tile, tileCoordKey, loaded; + for (var x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (var y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoordKey = this.getKeyZXY(z, x, y); + loaded = false; + if (tileCache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey)); + loaded = tile.getState() === ol.TileState.LOADED; + if (loaded) { + loaded = (callback(tile) !== false); + } + } + if (!loaded) { + covered = false; + } + } } - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.htmlEscape(textAsString), dir); + return covered; }; /** - * Returns HTML-escaped text as a SafeHtml object, with newlines changed to - * <br>. - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + * @param {ol.proj.Projection} projection Projection. + * @return {number} Gutter. */ -goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; - } - var html = goog.html.SafeHtml.htmlEscape(textOrHtml); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)), - html.getDirection()); +ol.source.Tile.prototype.getGutter = function(projection) { + return 0; }; /** - * Returns HTML-escaped text as a SafeHtml object, with newlines changed to - * <br> and escaping whitespace to preserve spatial formatting. Character - * entity #160 is used to make it safer for XML. - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + * Return the key to be used for all tiles in the source. + * @return {string} The key for all tiles. + * @protected */ -goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function( - textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; - } - var html = goog.html.SafeHtml.htmlEscape(textOrHtml); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)), - html.getDirection()); +ol.source.Tile.prototype.getKey = function() { + return this.key_; }; /** - * Coerces an arbitrary object into a SafeHtml object. - * - * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same - * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and - * HTML-escaped. If {@code textOrHtml} is of a type that implements - * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is - * preserved. - * - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to - * coerce. - * @return {!goog.html.SafeHtml} The resulting SafeHtml object. - * @deprecated Use goog.html.SafeHtml.htmlEscape. + * Set the value to be used as the key for all tiles in the source. + * @param {string} key The key for tiles. + * @protected */ -goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape; +ol.source.Tile.prototype.setKey = function(key) { + if (this.key_ !== key) { + this.key_ = key; + this.changed(); + } +}; /** - * @const - * @private + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + * @protected */ -goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/; +ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; /** - * Set of attributes containing URL as defined at - * http://www.w3.org/TR/html5/index.html#attributes-1. - * @private @const {!Object} + * @param {ol.proj.Projection} projection Projection. + * @return {boolean} Opaque. */ -goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet( - 'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster', - 'src'); +ol.source.Tile.prototype.getOpaque = function(projection) { + return this.opaque_; +}; /** - * Tags which are unsupported via create(). They might be supported via a - * tag-specific create method. These are tags which might require a - * TrustedResourceUrl in one of their attributes or a restricted type for - * their content. - * @private @const {!Object} + * @inheritDoc */ -goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet( - goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED, - goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH, - goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT, - goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE); +ol.source.Tile.prototype.getResolutions = function() { + return this.tileGrid.getResolutions(); +}; /** - * @typedef {string|number|goog.string.TypedString| - * goog.html.SafeStyle.PropertyMap} + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.Tile} Tile. */ -goog.html.SafeHtml.AttributeValue; +ol.source.Tile.prototype.getTile = goog.abstractMethod; /** - * Creates a SafeHtml content consisting of a tag with optional attributes and - * optional content. - * - * For convenience tag names and attribute names are accepted as regular - * strings, instead of goog.string.Const. Nevertheless, you should not pass - * user-controlled values to these parameters. Note that these parameters are - * syntactically validated at runtime, and invalid values will result in - * an exception. - * - * Example usage: - * - * goog.html.SafeHtml.create('br'); - * goog.html.SafeHtml.create('div', {'class': 'a'}); - * goog.html.SafeHtml.create('p', {}, 'a'); - * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br')); - * - * goog.html.SafeHtml.create('span', { - * 'style': {'margin': '0'} - * }); - * - * To guarantee SafeHtml's type contract is upheld there are restrictions on - * attribute values and tag names. - * - * - For attributes which contain script code (on*), a goog.string.Const is - * required. - * - For attributes which contain style (style), a goog.html.SafeStyle or a - * goog.html.SafeStyle.PropertyMap is required. - * - For attributes which are interpreted as URLs (e.g. src, href) a - * goog.html.SafeUrl, goog.string.Const or string is required. If a string - * is passed, it will be sanitized with SafeUrl.sanitize(). - * - For tags which can load code or set security relevant page metadata, - * more specific goog.html.SafeHtml.create*() functions must be used. Tags - * which are not supported by this function are applet, base, embed, iframe, - * link, math, object, script, style, svg, and template. - * - * @param {string} tagName The name of the tag. Only tag names consisting of - * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed. - * @param {!Object=} opt_attributes - * Mapping from attribute names to their values. Only attribute names - * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes - * the attribute to be omitted. - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array=} opt_content Content to - * HTML-escape and put inside the tag. This must be empty for void tags - * like
. Array elements are concatenated. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid tag name, attribute name, or attribute value is - * provided. - * @throws {goog.asserts.AssertionError} If content for void tag is provided. - */ -goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) { - goog.html.SafeHtml.verifyTagName(tagName); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - tagName, opt_attributes, opt_content); -}; - - -/** - * Verifies if the tag name is valid and if it doesn't change the context. - * E.g. STRONG is fine but SCRIPT throws because it changes context. See - * goog.html.SafeHtml.create for an explanation of allowed tags. - * @param {string} tagName - * @throws {Error} If invalid tag name is provided. - * @package - */ -goog.html.SafeHtml.verifyTagName = function(tagName) { - if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) { - throw Error('Invalid tag name <' + tagName + '>.'); - } - if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) { - throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.'); - } -}; - - -/** - * Creates a SafeHtml representing an iframe tag. - * - * This by default restricts the iframe as much as possible by setting the - * sandbox attribute to the empty string. If the iframe requires less - * restrictions, set the sandbox attribute as tight as possible, but do not rely - * on the sandbox as a security feature because it is not supported by older - * browsers. If a sandbox is essential to security (e.g. for third-party - * frames), use createSandboxIframe which checks for browser support. - * - * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox - * - * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src - * attribute. If null or undefined src will not be set. - * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute. - * If null or undefined srcdoc will not be set. - * @param {!Object=} opt_attributes - * Mapping from attribute names to their values. Only attribute names - * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes - * the attribute to be omitted. - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array=} opt_content Content to - * HTML-escape and put inside the tag. Array elements are concatenated. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid tag name, attribute name, or attribute value is - * provided. If opt_attributes contains the src or srcdoc attributes. - */ -goog.html.SafeHtml.createIframe = function( - opt_src, opt_srcdoc, opt_attributes, opt_content) { - if (opt_src) { - // Check whether this is really TrustedResourceUrl. - goog.html.TrustedResourceUrl.unwrap(opt_src); - } - - var fixedAttributes = {}; - fixedAttributes['src'] = opt_src || null; - fixedAttributes['srcdoc'] = - opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc); - var defaultAttributes = {'sandbox': ''}; - var attributes = goog.html.SafeHtml.combineAttributes( - fixedAttributes, defaultAttributes, opt_attributes); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'iframe', attributes, opt_content); -}; - - -/** - * Creates a SafeHtml representing a sandboxed iframe tag. - * - * The sandbox attribute is enforced in its most restrictive mode, an empty - * string. Consequently, the security requirements for the src and srcdoc - * attributes are relaxed compared to SafeHtml.createIframe. This function - * will throw on browsers that do not support the sandbox attribute, as - * determined by SafeHtml.canUseSandboxIframe. - * - * The SafeHtml returned by this function can trigger downloads with no - * user interaction on Chrome (though only a few, further attempts are blocked). - * Firefox and IE will block all downloads from the sandbox. - * - * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox - * @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html - * - * @param {string|!goog.html.SafeUrl=} opt_src The value of the src - * attribute. If null or undefined src will not be set. - * @param {string=} opt_srcdoc The value of the srcdoc attribute. - * If null or undefined srcdoc will not be set. Will not be sanitized. - * @param {!Object=} opt_attributes - * Mapping from attribute names to their values. Only attribute names - * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes - * the attribute to be omitted. - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array=} opt_content Content to - * HTML-escape and put inside the tag. Array elements are concatenated. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid tag name, attribute name, or attribute value is - * provided. If opt_attributes contains the src, srcdoc or sandbox - * attributes. If browser does not support the sandbox attribute on iframe. + * Return the tile grid of the tile source. + * @return {ol.tilegrid.TileGrid} Tile grid. + * @api stable */ -goog.html.SafeHtml.createSandboxIframe = function( - opt_src, opt_srcdoc, opt_attributes, opt_content) { - if (!goog.html.SafeHtml.canUseSandboxIframe()) { - throw new Error('The browser does not support sandboxed iframes.'); - } - - var fixedAttributes = {}; - if (opt_src) { - // Note that sanitize is a no-op on SafeUrl. - fixedAttributes['src'] = - goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src)); - } else { - fixedAttributes['src'] = null; - } - fixedAttributes['srcdoc'] = opt_srcdoc || null; - fixedAttributes['sandbox'] = ''; - var attributes = - goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'iframe', attributes, opt_content); +ol.source.Tile.prototype.getTileGrid = function() { + return this.tileGrid; }; /** - * Checks if the user agent supports sandboxed iframes. - * @return {boolean} + * @param {ol.proj.Projection} projection Projection. + * @return {ol.tilegrid.TileGrid} Tile grid. */ -goog.html.SafeHtml.canUseSandboxIframe = function() { - return goog.global['HTMLIFrameElement'] && - ('sandbox' in goog.global['HTMLIFrameElement'].prototype); -}; - - -/** - * Creates a SafeHtml representing a script tag with the src attribute. - * @param {!goog.html.TrustedResourceUrl} src The value of the src - * attribute. - * @param {!Object=} - * opt_attributes - * Mapping from attribute names to their values. Only attribute names - * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined - * causes the attribute to be omitted. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid attribute name or value is provided. If - * opt_attributes contains the src attribute. - */ -goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) { - // Check whether this is really TrustedResourceUrl. - goog.html.TrustedResourceUrl.unwrap(src); - - var fixedAttributes = {'src': src}; - var defaultAttributes = {}; - var attributes = goog.html.SafeHtml.combineAttributes( - fixedAttributes, defaultAttributes, opt_attributes); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'script', attributes); -}; - - -/** - * Creates a SafeHtml representing a style tag. The type attribute is set - * to "text/css". - * @param {!goog.html.SafeStyleSheet|!Array} - * styleSheet Content to put inside the tag. Array elements are - * concatenated. - * @param {!Object=} opt_attributes - * Mapping from attribute names to their values. Only attribute names - * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes - * the attribute to be omitted. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid attribute name or attribute value is provided. If - * opt_attributes contains the type attribute. - */ -goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) { - var fixedAttributes = {'type': 'text/css'}; - var defaultAttributes = {}; - var attributes = goog.html.SafeHtml.combineAttributes( - fixedAttributes, defaultAttributes, opt_attributes); - - var content = ''; - styleSheet = goog.array.concat(styleSheet); - for (var i = 0; i < styleSheet.length; i++) { - content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]); - } - // Convert to SafeHtml so that it's not HTML-escaped. This is safe because - // as part of its contract, SafeStyleSheet should have no dangerous '<'. - var htmlContent = - goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - content, goog.i18n.bidi.Dir.NEUTRAL); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'style', attributes, htmlContent); -}; - - -/** - * Creates a SafeHtml representing a meta refresh tag. - * @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is - * passed, it will be sanitized with SafeUrl.sanitize(). - * @param {number=} opt_secs Number of seconds until the page should be - * reloaded. Will be set to 0 if unspecified. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - */ -goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) { - - // Note that sanitize is a no-op on SafeUrl. - var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url)); - - if (goog.labs.userAgent.browser.isIE() || - goog.labs.userAgent.browser.isEdge()) { - // IE/EDGE can't parse the content attribute if the url contains a - // semicolon. We can fix this by adding quotes around the url, but then we - // can't parse quotes in the URL correctly. Also, it seems that IE/EDGE - // did not unescape semicolons in these URLs at some point in the past. We - // take a best-effort approach. - // - // If the URL has semicolons (which may happen in some cases, see - // http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2 - // for instance), wrap it in single quotes to protect the semicolons. - // If the URL has semicolons and single quotes, url-encode the single quotes - // as well. - // - // This is imperfect. Notice that both ' and ; are reserved characters in - // URIs, so this could do the wrong thing, but at least it will do the wrong - // thing in only rare cases. - if (goog.string.contains(unwrappedUrl, ';')) { - unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'"; - } +ol.source.Tile.prototype.getTileGridForProjection = function(projection) { + if (!this.tileGrid) { + return ol.tilegrid.getForProjection(projection); + } else { + return this.tileGrid; } - var attributes = { - 'http-equiv': 'refresh', - 'content': (opt_secs || 0) + '; url=' + unwrappedUrl - }; - - // This function will handle the HTML escaping for attributes. - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'meta', attributes); }; /** - * @param {string} tagName The tag name. - * @param {string} name The attribute name. - * @param {!goog.html.SafeHtml.AttributeValue} value The attribute value. - * @return {string} A "name=value" string. - * @throws {Error} If attribute value is unsafe for the given tag and attribute. - * @private + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCache} Tile cache. + * @protected */ -goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) { - // If it's goog.string.Const, allow any valid attribute name. - if (value instanceof goog.string.Const) { - value = goog.string.Const.unwrap(value); - } else if (name.toLowerCase() == 'style') { - value = goog.html.SafeHtml.getStyleValue_(value); - } else if (/^on/i.test(name)) { - // TODO(jakubvrana): Disallow more attributes with a special meaning. - throw Error( - 'Attribute "' + name + '" requires goog.string.Const value, "' + value + - '" given.'); - // URL attributes handled differently accroding to tag. - } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) { - if (value instanceof goog.html.TrustedResourceUrl) { - value = goog.html.TrustedResourceUrl.unwrap(value); - } else if (value instanceof goog.html.SafeUrl) { - value = goog.html.SafeUrl.unwrap(value); - } else if (goog.isString(value)) { - value = goog.html.SafeUrl.sanitize(value).getTypedStringValue(); - } else { - throw Error( - 'Attribute "' + name + '" on tag "' + tagName + - '" requires goog.html.SafeUrl, goog.string.Const, or string,' + - ' value "' + value + '" given.'); - } - } - - // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require - // HTML-escaping. - if (value.implementsGoogStringTypedString) { - // Ok to call getTypedStringValue() since there's no reliance on the type - // contract for security here. - value = value.getTypedStringValue(); +ol.source.Tile.prototype.getTileCacheForProjection = function(projection) { + var thisProj = this.getProjection(); + if (thisProj && !ol.proj.equivalent(thisProj, projection)) { + return null; + } else { + return this.tileCache; } - - goog.asserts.assert( - goog.isString(value) || goog.isNumber(value), - 'String or number value expected, got ' + (typeof value) + - ' with value: ' + value); - return name + '="' + goog.string.htmlEscape(String(value)) + '"'; }; /** - * Gets value allowed in "style" attribute. - * @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a - * map which will be passed to goog.html.SafeStyle.create. - * @return {string} Unwrapped value. - * @throws {Error} If string value is given. - * @private + * @param {number} pixelRatio Pixel ratio. + * @return {number} Tile pixel ratio. */ -goog.html.SafeHtml.getStyleValue_ = function(value) { - if (!goog.isObject(value)) { - throw Error( - 'The "style" attribute requires goog.html.SafeStyle or map ' + - 'of style properties, ' + (typeof value) + ' given: ' + value); - } - if (!(value instanceof goog.html.SafeStyle)) { - // Process the property bag into a style object. - value = goog.html.SafeStyle.create(value); - } - return goog.html.SafeStyle.unwrap(value); +ol.source.Tile.prototype.getTilePixelRatio = function(pixelRatio) { + return this.tilePixelRatio_; }; /** - * Creates a SafeHtml content with known directionality consisting of a tag with - * optional attributes and optional content. - * @param {!goog.i18n.bidi.Dir} dir Directionality. - * @param {string} tagName - * @param {!Object=} opt_attributes - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array=} opt_content - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @param {number} z Z. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.Size} Tile size. */ -goog.html.SafeHtml.createWithDir = function( - dir, tagName, opt_attributes, opt_content) { - var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content); - html.dir_ = dir; - return html; +ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileGrid = this.getTileGridForProjection(projection); + var tilePixelRatio = this.getTilePixelRatio(pixelRatio); + var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize); + if (tilePixelRatio == 1) { + return tileSize; + } else { + return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize); + } }; /** - * Creates a new SafeHtml object by concatenating values. - * @param {...(!goog.html.SafeHtml.TextOrHtml_| - * !Array)} var_args Values to concatenate. - * @return {!goog.html.SafeHtml} + * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection=} opt_projection Projection. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. */ -goog.html.SafeHtml.concat = function(var_args) { - var dir = goog.i18n.bidi.Dir.NEUTRAL; - var content = ''; - - /** - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array} argument - */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - var html = goog.html.SafeHtml.htmlEscape(argument); - content += goog.html.SafeHtml.unwrap(html); - var htmlDir = html.getDirection(); - if (dir == goog.i18n.bidi.Dir.NEUTRAL) { - dir = htmlDir; - } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) { - dir = null; - } - } - }; - - goog.array.forEach(arguments, addArgument); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - content, dir); +ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) { + var projection = opt_projection !== undefined ? + opt_projection : this.getProjection(); + var tileGrid = this.getTileGridForProjection(projection); + goog.asserts.assert(tileGrid, 'tile grid needed'); + if (this.getWrapX() && projection.isGlobal()) { + tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null; }; /** - * Creates a new SafeHtml object with known directionality by concatenating the - * values. - * @param {!goog.i18n.bidi.Dir} dir Directionality. - * @param {...(!goog.html.SafeHtml.TextOrHtml_| - * !Array)} var_args Elements of array - * arguments would be processed recursively. - * @return {!goog.html.SafeHtml} + * @inheritDoc */ -goog.html.SafeHtml.concatWithDir = function(dir, var_args) { - var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1)); - html.dir_ = dir; - return html; +ol.source.Tile.prototype.refresh = function() { + this.tileCache.clear(); + this.changed(); }; /** - * Type marker for the SafeHtml type, used to implement additional run-time - * type checking. - * @const {!Object} - * @private + * Marks a tile coord as being used, without triggering a load. + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {ol.proj.Projection} projection Projection. */ -goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.source.Tile.prototype.useTile = ol.nullFunction; /** - * Package-internal utility method to create SafeHtml instances. + * @classdesc + * Events emitted by {@link ol.source.Tile} instances are instances of this + * type. * - * @param {string} html The string to initialize the SafeHtml object with. - * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be - * constructed, or null if unknown. - * @return {!goog.html.SafeHtml} The initialized SafeHtml object. - * @package + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.TileEvent} + * @param {string} type Type. + * @param {ol.Tile} tile The tile. */ -goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function( - html, dir) { - return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_( - html, dir); -}; +ol.source.TileEvent = function(type, tile) { + ol.events.Event.call(this, type); + + /** + * The tile related to the event. + * @type {ol.Tile} + * @api + */ + this.tile = tile; -/** - * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This - * method exists only so that the compiler can dead code eliminate static - * fields (like EMPTY) when they're not accessed. - * @param {string} html - * @param {?goog.i18n.bidi.Dir} dir - * @return {!goog.html.SafeHtml} - * @private - */ -goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function( - html, dir) { - this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html; - this.dir_ = dir; - return this; }; +ol.inherits(ol.source.TileEvent, ol.events.Event); /** - * Like create() but does not restrict which tags can be constructed. - * - * @param {string} tagName Tag name. Set or validated by caller. - * @param {!Object=} opt_attributes - * @param {(!goog.html.SafeHtml.TextOrHtml_| - * !Array)=} opt_content - * @return {!goog.html.SafeHtml} - * @throws {Error} If invalid or unsafe attribute name or value is provided. - * @throws {goog.asserts.AssertionError} If content for void tag is provided. - * @package + * @enum {string} */ -goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function( - tagName, opt_attributes, opt_content) { - var dir = null; - var result = '<' + tagName; - result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes); +ol.source.TileEventType = { - var content = opt_content; - if (!goog.isDefAndNotNull(content)) { - content = []; - } else if (!goog.isArray(content)) { - content = [content]; - } + /** + * Triggered when a tile starts loading. + * @event ol.source.TileEvent#tileloadstart + * @api stable + */ + TILELOADSTART: 'tileloadstart', - if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) { - goog.asserts.assert( - !content.length, 'Void tag <' + tagName + '> does not allow content.'); - result += '>'; - } else { - var html = goog.html.SafeHtml.concat(content); - result += '>' + goog.html.SafeHtml.unwrap(html) + ''; - dir = html.getDirection(); - } + /** + * Triggered when a tile finishes loading. + * @event ol.source.TileEvent#tileloadend + * @api stable + */ + TILELOADEND: 'tileloadend', - var dirAttribute = opt_attributes && opt_attributes['dir']; - if (dirAttribute) { - if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) { - // If the tag has the "dir" attribute specified then its direction is - // neutral because it can be safely used in any context. - dir = goog.i18n.bidi.Dir.NEUTRAL; - } else { - dir = null; - } - } + /** + * Triggered if tile loading results in an error. + * @event ol.source.TileEvent#tileloaderror + * @api stable + */ + TILELOADERROR: 'tileloaderror' - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - result, dir); }; +// FIXME handle date line wrap -/** - * Creates a string with attributes to insert after tagName. - * @param {string} tagName - * @param {!Object=} opt_attributes - * @return {string} Returns an empty string if there are no attributes, returns - * a string starting with a space otherwise. - * @throws {Error} If attribute value is unsafe for the given tag and attribute. - * @package - */ -goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) { - var result = ''; - if (opt_attributes) { - for (var name in opt_attributes) { - if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) { - throw Error('Invalid attribute name "' + name + '".'); - } - var value = opt_attributes[name]; - if (!goog.isDefAndNotNull(value)) { - continue; - } - result += - ' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value); - } - } - return result; -} +goog.provide('ol.control.Attribution'); + +goog.require('goog.asserts'); +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.Attribution'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.object'); +goog.require('ol.source.Tile'); /** - * @param {!Object} fixedAttributes - * @param {!Object} defaultAttributes - * @param {!Object=} opt_attributes - * Optional attributes passed to create*(). - * @return {!Object} - * @throws {Error} If opt_attributes contains an attribute with the same name - * as an attribute in fixedAttributes. - * @package + * @classdesc + * Control to show all the attributions associated with the layer sources + * in the map. This control is one of the default controls included in maps. + * By default it will show in the bottom right portion of the map, but this can + * be changed by using a css selector for `.ol-attribution`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.AttributionOptions=} opt_options Attribution options. + * @api stable */ -goog.html.SafeHtml.combineAttributes = function( - fixedAttributes, defaultAttributes, opt_attributes) { - var combinedAttributes = {}; - var name; +ol.control.Attribution = function(opt_options) { - for (name in fixedAttributes) { - goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); - combinedAttributes[name] = fixedAttributes[name]; - } - for (name in defaultAttributes) { - goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); - combinedAttributes[name] = defaultAttributes[name]; - } + var options = opt_options ? opt_options : {}; - for (name in opt_attributes) { - var nameLower = name.toLowerCase(); - if (nameLower in fixedAttributes) { - throw Error( - 'Cannot override "' + nameLower + '" attribute, got "' + name + - '" with value "' + opt_attributes[name] + '"'); - } - if (nameLower in defaultAttributes) { - delete combinedAttributes[nameLower]; - } - combinedAttributes[name] = opt_attributes[name]; + /** + * @private + * @type {Element} + */ + this.ulElement_ = document.createElement('UL'); + + /** + * @private + * @type {Element} + */ + this.logoLi_ = document.createElement('LI'); + + this.ulElement_.appendChild(this.logoLi_); + this.logoLi_.style.display = 'none'; + + /** + * @private + * @type {boolean} + */ + this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + + /** + * @private + * @type {boolean} + */ + this.collapsible_ = options.collapsible !== undefined ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; } - return combinedAttributes; -}; + var className = options.className !== undefined ? options.className : 'ol-attribution'; + var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions'; -/** - * A SafeHtml instance corresponding to the HTML doctype: "". - * @const {!goog.html.SafeHtml} - */ -goog.html.SafeHtml.DOCTYPE_HTML = - goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - '', goog.i18n.bidi.Dir.NEUTRAL); + var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB'; + if (typeof collapseLabel === 'string') { + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = document.createElement('span'); + this.collapseLabel_.textContent = collapseLabel; + } else { + this.collapseLabel_ = collapseLabel; + } -/** - * A SafeHtml instance corresponding to the empty string. - * @const {!goog.html.SafeHtml} - */ -goog.html.SafeHtml.EMPTY = - goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - '', goog.i18n.bidi.Dir.NEUTRAL); + var label = options.label !== undefined ? options.label : 'i'; + if (typeof label === 'string') { + /** + * @private + * @type {Node} + */ + this.label_ = document.createElement('span'); + this.label_.textContent = label; + } else { + this.label_ = label; + } -/** - * A SafeHtml instance corresponding to the
tag. - * @const {!goog.html.SafeHtml} - */ -goog.html.SafeHtml.BR = - goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - '
', goog.i18n.bidi.Dir.NEUTRAL); -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(activeLabel); -/** - * @fileoverview Type-safe wrappers for unsafe DOM APIs. - * - * This file provides type-safe wrappers for DOM APIs that can result in - * cross-site scripting (XSS) vulnerabilities, if the API is supplied with - * untrusted (attacker-controlled) input. Instead of plain strings, the type - * safe wrappers consume values of types from the goog.html package whose - * contract promises that values are safe to use in the corresponding context. - * - * Hence, a program that exclusively uses the wrappers in this file (i.e., whose - * only reference to security-sensitive raw DOM APIs are in this file) is - * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo - * correctness of code that produces values of the respective goog.html types, - * and absent code that violates type safety). - * - * For example, assigning to an element's .innerHTML property a string that is - * derived (even partially) from untrusted input typically results in an XSS - * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value - * of type goog.html.SafeHtml, whose contract states that using its values in a - * HTML context will not result in XSS. Hence a program that is free of direct - * assignments to any element's innerHTML property (with the exception of the - * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to - * assignment of untrusted strings to the innerHTML property. - */ + ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this); -goog.provide('goog.dom.safe'); -goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition'); + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(this.ulElement_); + element.appendChild(button); -goog.require('goog.asserts'); -goog.require('goog.html.SafeHtml'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.string'); -goog.require('goog.string.Const'); + var render = options.render ? options.render : ol.control.Attribution.render; + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); -/** @enum {string} */ -goog.dom.safe.InsertAdjacentHtmlPosition = { - AFTERBEGIN: 'afterbegin', - AFTEREND: 'afterend', - BEFOREBEGIN: 'beforebegin', - BEFOREEND: 'beforeend' -}; + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + /** + * @private + * @type {Object.} + */ + this.attributionElements_ = {}; -/** - * Inserts known-safe HTML into a Node, at the specified position. - * @param {!Node} node The node on which to call insertAdjacentHTML. - * @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where - * to insert the HTML. - * @param {!goog.html.SafeHtml} html The known-safe HTML to insert. - */ -goog.dom.safe.insertAdjacentHtml = function(node, position, html) { - node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(html)); -}; + /** + * @private + * @type {Object.} + */ + this.attributionElementRenderedVisible_ = {}; + /** + * @private + * @type {Object.} + */ + this.logoElements_ = {}; -/** - * Assigns known-safe HTML to an element's innerHTML property. - * @param {!Element} elem The element whose innerHTML is to be assigned to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. - */ -goog.dom.safe.setInnerHtml = function(elem, html) { - elem.innerHTML = goog.html.SafeHtml.unwrap(html); }; +ol.inherits(ol.control.Attribution, ol.control.Control); /** - * Assigns known-safe HTML to an element's outerHTML property. - * @param {!Element} elem The element whose outerHTML is to be assigned to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * @param {?olx.FrameState} frameState Frame state. + * @return {Array.>} Attributions. */ -goog.dom.safe.setOuterHtml = function(elem, html) { - elem.outerHTML = goog.html.SafeHtml.unwrap(html); +ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { + var i, ii, j, jj, tileRanges, source, sourceAttribution, + sourceAttributionKey, sourceAttributions, sourceKey; + var intersectsTileRange; + var layerStatesArray = frameState.layerStatesArray; + /** @type {Object.} */ + var attributions = ol.object.assign({}, frameState.attributions); + /** @type {Object.} */ + var hiddenAttributions = {}; + var projection = frameState.viewState.projection; + goog.asserts.assert(projection, 'projection of viewState required'); + for (i = 0, ii = layerStatesArray.length; i < ii; i++) { + source = layerStatesArray[i].layer.getSource(); + if (!source) { + continue; + } + sourceKey = goog.getUid(source).toString(); + sourceAttributions = source.getAttributions(); + if (!sourceAttributions) { + continue; + } + for (j = 0, jj = sourceAttributions.length; j < jj; j++) { + sourceAttribution = sourceAttributions[j]; + sourceAttributionKey = goog.getUid(sourceAttribution).toString(); + if (sourceAttributionKey in attributions) { + continue; + } + tileRanges = frameState.usedTiles[sourceKey]; + if (tileRanges) { + goog.asserts.assertInstanceof(source, ol.source.Tile, + 'source should be an ol.source.Tile'); + var tileGrid = source.getTileGridForProjection(projection); + goog.asserts.assert(tileGrid, 'tileGrid required for projection'); + intersectsTileRange = sourceAttribution.intersectsAnyTileRange( + tileRanges, tileGrid, projection); + } else { + intersectsTileRange = false; + } + if (intersectsTileRange) { + if (sourceAttributionKey in hiddenAttributions) { + delete hiddenAttributions[sourceAttributionKey]; + } + attributions[sourceAttributionKey] = sourceAttribution; + } else { + hiddenAttributions[sourceAttributionKey] = sourceAttribution; + } + } + } + return [attributions, hiddenAttributions]; }; /** - * Writes known-safe HTML to a document. - * @param {!Document} doc The document to be written to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * Update the attribution element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Attribution} + * @api */ -goog.dom.safe.documentWrite = function(doc, html) { - doc.write(goog.html.SafeHtml.unwrap(html)); +ol.control.Attribution.render = function(mapEvent) { + this.updateElement_(mapEvent.frameState); }; /** - * Safely assigns a URL to an anchor element's href property. - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to - * anchor's href property. If url is of type string however, it is first - * sanitized using goog.html.SafeUrl.sanitize. - * - * Example usage: - * goog.dom.safe.setAnchorHref(anchorEl, url); - * which is a safe alternative to - * anchorEl.href = url; - * The latter can result in XSS vulnerabilities if url is a - * user-/attacker-controlled value. - * - * @param {!HTMLAnchorElement} anchor The anchor element whose href property - * is to be assigned to. - * @param {string|!goog.html.SafeUrl} url The URL to assign. - * @see goog.html.SafeUrl#sanitize + * @private + * @param {?olx.FrameState} frameState Frame state. */ -goog.dom.safe.setAnchorHref = function(anchor, url) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; - } else { - safeUrl = goog.html.SafeUrl.sanitize(url); +ol.control.Attribution.prototype.updateElement_ = function(frameState) { + + if (!frameState) { + if (this.renderedVisible_) { + this.element.style.display = 'none'; + this.renderedVisible_ = false; + } + return; } - anchor.href = goog.html.SafeUrl.unwrap(safeUrl); -}; + var attributions = this.getSourceAttributions(frameState); + /** @type {Object.} */ + var visibleAttributions = attributions[0]; + /** @type {Object.} */ + var hiddenAttributions = attributions[1]; -/** - * Safely assigns a URL to an image element's src property. - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to - * image's src property. If url is of type string however, it is first - * sanitized using goog.html.SafeUrl.sanitize. - * - * @param {!HTMLImageElement} imageElement The image element whose src property - * is to be assigned to. - * @param {string|!goog.html.SafeUrl} url The URL to assign. - * @see goog.html.SafeUrl#sanitize - */ -goog.dom.safe.setImageSrc = function(imageElement, url) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; + var attributionElement, attributionKey; + for (attributionKey in this.attributionElements_) { + if (attributionKey in visibleAttributions) { + if (!this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = ''; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + delete visibleAttributions[attributionKey]; + } else if (attributionKey in hiddenAttributions) { + if (this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = 'none'; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + delete hiddenAttributions[attributionKey]; + } else { + ol.dom.removeNode(this.attributionElements_[attributionKey]); + delete this.attributionElements_[attributionKey]; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + } + for (attributionKey in visibleAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + visibleAttributions[attributionKey].getHTML(); + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + for (attributionKey in hiddenAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + hiddenAttributions[attributionKey].getHTML(); + attributionElement.style.display = 'none'; + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + } + + var renderVisible = + !ol.object.isEmpty(this.attributionElementRenderedVisible_) || + !ol.object.isEmpty(frameState.logos); + if (this.renderedVisible_ != renderVisible) { + this.element.style.display = renderVisible ? '' : 'none'; + this.renderedVisible_ = renderVisible; + } + if (renderVisible && + ol.object.isEmpty(this.attributionElementRenderedVisible_)) { + this.element.classList.add('ol-logo-only'); } else { - safeUrl = goog.html.SafeUrl.sanitize(url); + this.element.classList.remove('ol-logo-only'); } - imageElement.src = goog.html.SafeUrl.unwrap(safeUrl); -}; + this.insertLogos_(frameState); -/** - * Safely assigns a URL to an embed element's src property. - * - * Example usage: - * goog.dom.safe.setEmbedSrc(embedEl, url); - * which is a safe alternative to - * embedEl.src = url; - * The latter can result in loading untrusted code unless it is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLEmbedElement} embed The embed element whose src property - * is to be assigned to. - * @param {!goog.html.TrustedResourceUrl} url The URL to assign. - */ -goog.dom.safe.setEmbedSrc = function(embed, url) { - embed.src = goog.html.TrustedResourceUrl.unwrap(url); }; /** - * Safely assigns a URL to a frame element's src property. - * - * Example usage: - * goog.dom.safe.setFrameSrc(frameEl, url); - * which is a safe alternative to - * frameEl.src = url; - * The latter can result in loading untrusted code unless it is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLFrameElement} frame The frame element whose src property - * is to be assigned to. - * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + * @param {?olx.FrameState} frameState Frame state. + * @private */ -goog.dom.safe.setFrameSrc = function(frame, url) { - frame.src = goog.html.TrustedResourceUrl.unwrap(url); -}; +ol.control.Attribution.prototype.insertLogos_ = function(frameState) { + var logo; + var logos = frameState.logos; + var logoElements = this.logoElements_; + + for (logo in logoElements) { + if (!(logo in logos)) { + ol.dom.removeNode(logoElements[logo]); + delete logoElements[logo]; + } + } + + var image, logoElement, logoKey; + for (logoKey in logos) { + var logoValue = logos[logoKey]; + if (logoValue instanceof HTMLElement) { + this.logoLi_.appendChild(logoValue); + logoElements[logoKey] = logoValue; + } + if (!(logoKey in logoElements)) { + image = new Image(); + image.src = logoKey; + if (logoValue === '') { + logoElement = image; + } else { + logoElement = document.createElement('a'); + logoElement.href = logoValue; + logoElement.appendChild(image); + } + this.logoLi_.appendChild(logoElement); + logoElements[logoKey] = logoElement; + } + } + + this.logoLi_.style.display = !ol.object.isEmpty(logos) ? '' : 'none'; -/** - * Safely assigns a URL to an iframe element's src property. - * - * Example usage: - * goog.dom.safe.setIframeSrc(iframeEl, url); - * which is a safe alternative to - * iframeEl.src = url; - * The latter can result in loading untrusted code unless it is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLIFrameElement} iframe The iframe element whose src property - * is to be assigned to. - * @param {!goog.html.TrustedResourceUrl} url The URL to assign. - */ -goog.dom.safe.setIframeSrc = function(iframe, url) { - iframe.src = goog.html.TrustedResourceUrl.unwrap(url); }; /** - * Safely sets a link element's href and rel properties. Whether or not - * the URL assigned to href has to be a goog.html.TrustedResourceUrl - * depends on the value of the rel property. If rel contains "stylesheet" - * then a TrustedResourceUrl is required. - * - * Example usage: - * goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet'); - * which is a safe alternative to - * linkEl.rel = 'stylesheet'; - * linkEl.href = url; - * The latter can result in loading untrusted code unless it is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLLinkElement} link The link element whose href property - * is to be assigned to. - * @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL - * to assign to the href property. Must be a TrustedResourceUrl if the - * value assigned to rel contains "stylesheet". A string value is - * sanitized with goog.html.SafeUrl.sanitize. - * @param {string} rel The value to assign to the rel property. - * @throws {Error} if rel contains "stylesheet" and url is not a - * TrustedResourceUrl - * @see goog.html.SafeUrl#sanitize + * @param {Event} event The event to handle + * @private */ -goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) { - link.rel = rel; - if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) { - goog.asserts.assert( - url instanceof goog.html.TrustedResourceUrl, - 'URL must be TrustedResourceUrl because "rel" contains "stylesheet"'); - link.href = goog.html.TrustedResourceUrl.unwrap(url); - } else if (url instanceof goog.html.TrustedResourceUrl) { - link.href = goog.html.TrustedResourceUrl.unwrap(url); - } else if (url instanceof goog.html.SafeUrl) { - link.href = goog.html.SafeUrl.unwrap(url); - } else { // string - // SafeUrl.sanitize must return legitimate SafeUrl when passed a string. - link.href = goog.html.SafeUrl.sanitize(url).getTypedStringValue(); - } +ol.control.Attribution.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); }; /** - * Safely assigns a URL to an object element's data property. - * - * Example usage: - * goog.dom.safe.setObjectData(objectEl, url); - * which is a safe alternative to - * objectEl.data = url; - * The latter can result in loading untrusted code unless setit is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLObjectElement} object The object element whose data property - * is to be assigned to. - * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + * @private */ -goog.dom.safe.setObjectData = function(object, url) { - object.data = goog.html.TrustedResourceUrl.unwrap(url); +ol.control.Attribution.prototype.handleToggle_ = function() { + this.element.classList.toggle('ol-collapsed'); + if (this.collapsed_) { + ol.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + ol.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; }; /** - * Safely assigns a URL to an iframe element's src property. - * - * Example usage: - * goog.dom.safe.setScriptSrc(scriptEl, url); - * which is a safe alternative to - * scriptEl.src = url; - * The latter can result in loading untrusted code unless it is ensured that - * the URL refers to a trustworthy resource. - * - * @param {!HTMLScriptElement} script The script element whose src property - * is to be assigned to. - * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + * Return `true` if the attribution is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable */ -goog.dom.safe.setScriptSrc = function(script, url) { - script.src = goog.html.TrustedResourceUrl.unwrap(url); +ol.control.Attribution.prototype.getCollapsible = function() { + return this.collapsible_; }; /** - * Safely assigns a URL to a Location object's href property. - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to - * loc's href property. If url is of type string however, it is first sanitized - * using goog.html.SafeUrl.sanitize. - * - * Example usage: - * goog.dom.safe.setLocationHref(document.location, redirectUrl); - * which is a safe alternative to - * document.location.href = redirectUrl; - * The latter can result in XSS vulnerabilities if redirectUrl is a - * user-/attacker-controlled value. - * - * @param {!Location} loc The Location object whose href property is to be - * assigned to. - * @param {string|!goog.html.SafeUrl} url The URL to assign. - * @see goog.html.SafeUrl#sanitize + * Set whether the attribution should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable */ -goog.dom.safe.setLocationHref = function(loc, url) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; - } else { - safeUrl = goog.html.SafeUrl.sanitize(url); +ol.control.Attribution.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + this.element.classList.toggle('ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); } - loc.href = goog.html.SafeUrl.unwrap(safeUrl); }; /** - * Safely opens a URL in a new window (via window.open). - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to - * window.open. If url is of type string however, it is first sanitized - * using goog.html.SafeUrl.sanitize. - * - * Note that this function does not prevent leakages via the referer that is - * sent by window.open. It is advised to only use this to open 1st party URLs. - * - * Example usage: - * goog.dom.safe.openInWindow(url); - * which is a safe alternative to - * window.open(url); - * The latter can result in XSS vulnerabilities if redirectUrl is a - * user-/attacker-controlled value. - * - * @param {string|!goog.html.SafeUrl} url The URL to open. - * @param {Window=} opt_openerWin Window of which to call the .open() method. - * Defaults to the global window. - * @param {!goog.string.Const=} opt_name Name of the window to open in. Can be - * _top, etc as allowed by window.open(). - * @param {string=} opt_specs Comma-separated list of specifications, same as - * in window.open(). - * @param {boolean=} opt_replace Whether to replace the current entry in browser - * history, same as in window.open(). - * @return {Window} Window the url was opened in. + * Collapse or expand the attribution according to the passed parameter. Will + * not do anything if the attribution isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable */ -goog.dom.safe.openInWindow = function( - url, opt_openerWin, opt_name, opt_specs, opt_replace) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; - } else { - safeUrl = goog.html.SafeUrl.sanitize(url); +ol.control.Attribution.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; } - var win = opt_openerWin || window; - return win.open( - goog.html.SafeUrl.unwrap(safeUrl), - // If opt_name is undefined, simply passing that in to open() causes IE to - // reuse the current window instead of opening a new one. Thus we pass '' - // in instead, which according to spec opens a new window. See - // https://html.spec.whatwg.org/multipage/browsers.html#dom-open . - opt_name ? goog.string.Const.unwrap(opt_name) : '', opt_specs, - opt_replace); + this.handleToggle_(); }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Transitional utilities to unsafely trust random strings as - * goog.html types. Intended for temporary use when upgrading a library that - * used to accept plain strings to use safe types, but where it's not - * practical to transitively update callers. - * - * IMPORTANT: No new code should use the conversion functions in this file, - * they are intended for refactoring old code to use goog.html types. New code - * should construct goog.html types via their APIs, template systems or - * sanitizers. If that’s not possible it should use - * goog.html.uncheckedconversions and undergo security review. - - * - * The semantics of the conversions in goog.html.legacyconversions are very - * different from the ones provided by goog.html.uncheckedconversions. The - * latter are for use in code where it has been established through manual - * security review that the value produced by a piece of code will always - * satisfy the SafeHtml contract (e.g., the output of a secure HTML sanitizer). - * In uses of goog.html.legacyconversions, this guarantee is not given -- the - * value in question originates in unreviewed legacy code and there is no - * guarantee that it satisfies the SafeHtml contract. - * - * There are only three valid uses of legacyconversions: - * - * 1. Introducing a goog.html version of a function which currently consumes - * string and passes that string to a DOM API which can execute script - and - * hence cause XSS - like innerHTML. For example, Dialog might expose a - * setContent method which takes a string and sets the innerHTML property of - * an element with it. In this case a setSafeHtmlContent function could be - * added, consuming goog.html.SafeHtml instead of string, and using - * goog.dom.safe.setInnerHtml instead of directly setting innerHTML. - * setContent could then internally use legacyconversions to create a SafeHtml - * from string and pass the SafeHtml to setSafeHtmlContent. In this scenario - * remember to document the use of legacyconversions in the modified setContent - * and consider deprecating it as well. - * - * 2. Automated refactoring of application code which handles HTML as string - * but needs to call a function which only takes goog.html types. For example, - * in the Dialog scenario from (1) an alternative option would be to refactor - * setContent to accept goog.html.SafeHtml instead of string and then refactor - * all current callers to use legacyconversions to pass SafeHtml. This is - * generally preferable to (1) because it keeps the library clean of - * legacyconversions, and makes code sites in application code that are - * potentially vulnerable to XSS more apparent. - * - * 3. Old code which needs to call APIs which consume goog.html types and for - * which it is prohibitively expensive to refactor to use goog.html types. - * Generally, this is code where safety from XSS is either hopeless or - * unimportant. - * - * @visibility {//closure/goog/html:approved_for_legacy_conversion} - * @visibility {//closure/goog/bin/sizetests:__pkg__} + * Return `true` when the attribution is currently collapsed or `false` + * otherwise. + * @return {boolean} True if the widget is collapsed. + * @api stable */ +ol.control.Attribution.prototype.getCollapsed = function() { + return this.collapsed_; +}; +goog.provide('ol.control.Rotate'); -goog.provide('goog.html.legacyconversions'); - -goog.require('goog.html.SafeHtml'); -goog.require('goog.html.SafeStyle'); -goog.require('goog.html.SafeStyleSheet'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.html.TrustedResourceUrl'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); /** - * Performs an "unchecked conversion" from string to SafeHtml for legacy API - * purposes. - * - * Please read fileoverview documentation before using. + * @classdesc + * A button control to reset rotation to 0. + * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css + * selector is added to the button when the rotation is 0. * - * @param {string} html A string to be converted to SafeHtml. - * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml - * object. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.RotateOptions=} opt_options Rotate options. + * @api stable */ -goog.html.legacyconversions.safeHtmlFromString = function(html) { - goog.html.legacyconversions.reportCallback_(); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - html, null /* dir */); -}; +ol.control.Rotate = function(opt_options) { + var options = opt_options ? opt_options : {}; -/** - * Performs an "unchecked conversion" from string to SafeStyle for legacy API - * purposes. - * - * Please read fileoverview documentation before using. - * - * @param {string} style A string to be converted to SafeStyle. - * @return {!goog.html.SafeStyle} The value of style, wrapped in a SafeStyle - * object. - */ -goog.html.legacyconversions.safeStyleFromString = function(style) { - goog.html.legacyconversions.reportCallback_(); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); -}; + var className = options.className !== undefined ? options.className : 'ol-rotate'; + var label = options.label !== undefined ? options.label : '\u21E7'; -/** - * Performs an "unchecked conversion" from string to SafeStyleSheet for legacy - * API purposes. - * - * Please read fileoverview documentation before using. - * - * @param {string} styleSheet A string to be converted to SafeStyleSheet. - * @return {!goog.html.SafeStyleSheet} The value of style sheet, wrapped in - * a SafeStyleSheet object. - */ -goog.html.legacyconversions.safeStyleSheetFromString = function(styleSheet) { - goog.html.legacyconversions.reportCallback_(); - return goog.html.SafeStyleSheet - .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); -}; + /** + * @type {Element} + * @private + */ + this.label_ = null; + if (typeof label === 'string') { + this.label_ = document.createElement('span'); + this.label_.className = 'ol-compass'; + this.label_.textContent = label; + } else { + this.label_ = label; + this.label_.classList.add('ol-compass'); + } -/** - * Performs an "unchecked conversion" from string to SafeUrl for legacy API - * purposes. - * - * Please read fileoverview documentation before using. - * - * @param {string} url A string to be converted to SafeUrl. - * @return {!goog.html.SafeUrl} The value of url, wrapped in a SafeUrl - * object. - */ -goog.html.legacyconversions.safeUrlFromString = function(url) { - goog.html.legacyconversions.reportCallback_(); - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); -}; + var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation'; + var button = document.createElement('button'); + button.className = className + '-reset'; + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.label_); -/** - * Performs an "unchecked conversion" from string to TrustedResourceUrl for - * legacy API purposes. - * - * Please read fileoverview documentation before using. - * - * @param {string} url A string to be converted to TrustedResourceUrl. - * @return {!goog.html.TrustedResourceUrl} The value of url, wrapped in a - * TrustedResourceUrl object. - */ -goog.html.legacyconversions.trustedResourceUrlFromString = function(url) { - goog.html.legacyconversions.reportCallback_(); - return goog.html.TrustedResourceUrl - .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); -}; + ol.events.listen(button, ol.events.EventType.CLICK, + ol.control.Rotate.prototype.handleClick_, this); -/** - * @private {function(): undefined} - */ -goog.html.legacyconversions.reportCallback_ = goog.nullFunction; + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + var render = options.render ? options.render : ol.control.Rotate.render; -/** - * Sets a function that will be called every time a legacy conversion is - * performed. The function is called with no parameters but it can use - * goog.debug.getStacktrace to get a stacktrace. - * - * @param {function(): undefined} callback Error callback as defined above. - */ -goog.html.legacyconversions.setReportCallback = function(callback) { - goog.html.legacyconversions.reportCallback_ = callback; -}; - -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview A utility class for representing two-dimensional positions. - */ - - -goog.provide('goog.math.Coordinate'); - -goog.require('goog.math'); - + this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined; + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); -/** - * Class for representing coordinates and positions. - * @param {number=} opt_x Left, defaults to 0. - * @param {number=} opt_y Top, defaults to 0. - * @struct - * @constructor - */ -goog.math.Coordinate = function(opt_x, opt_y) { /** - * X-value * @type {number} + * @private */ - this.x = goog.isDef(opt_x) ? opt_x : 0; + this.duration_ = options.duration !== undefined ? options.duration : 250; /** - * Y-value - * @type {number} + * @type {boolean} + * @private */ - this.y = goog.isDef(opt_y) ? opt_y : 0; -}; - - -/** - * Returns a new copy of the coordinate. - * @return {!goog.math.Coordinate} A clone of this coordinate. - */ -goog.math.Coordinate.prototype.clone = function() { - return new goog.math.Coordinate(this.x, this.y); -}; - + this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true; -if (goog.DEBUG) { /** - * Returns a nice string representing the coordinate. - * @return {string} In the form (50, 73). - * @override + * @private + * @type {number|undefined} */ - goog.math.Coordinate.prototype.toString = function() { - return '(' + this.x + ', ' + this.y + ')'; - }; -} - + this.rotation_ = undefined; -/** - * Compares coordinates for equality. - * @param {goog.math.Coordinate} a A Coordinate. - * @param {goog.math.Coordinate} b A Coordinate. - * @return {boolean} True iff the coordinates are equal, or if both are null. - */ -goog.math.Coordinate.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; + if (this.autoHide_) { + this.element.classList.add(ol.css.CLASS_HIDDEN); } - return a.x == b.x && a.y == b.y; + }; +ol.inherits(ol.control.Rotate, ol.control.Control); /** - * Returns the distance between two coordinates. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {number} The distance between {@code a} and {@code b}. + * @param {Event} event The event to handle + * @private */ -goog.math.Coordinate.distance = function(a, b) { - var dx = a.x - b.x; - var dy = a.y - b.y; - return Math.sqrt(dx * dx + dy * dy); +ol.control.Rotate.prototype.handleClick_ = function(event) { + event.preventDefault(); + if (this.callResetNorth_ !== undefined) { + this.callResetNorth_(); + } else { + this.resetNorth_(); + } }; /** - * Returns the magnitude of a coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @return {number} The distance between the origin and {@code a}. + * @private */ -goog.math.Coordinate.magnitude = function(a) { - return Math.sqrt(a.x * a.x + a.y * a.y); +ol.control.Rotate.prototype.resetNorth_ = function() { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentRotation = view.getRotation(); + if (currentRotation !== undefined) { + if (this.duration_ > 0) { + currentRotation = currentRotation % (2 * Math.PI); + if (currentRotation < -Math.PI) { + currentRotation += 2 * Math.PI; + } + if (currentRotation > Math.PI) { + currentRotation -= 2 * Math.PI; + } + map.beforeRender(ol.animation.rotate({ + rotation: currentRotation, + duration: this.duration_, + easing: ol.easing.easeOut + })); + } + view.setRotation(0); + } }; /** - * Returns the angle from the origin to a coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @return {number} The angle, in degrees, clockwise from the positive X - * axis to {@code a}. + * Update the rotate control element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Rotate} + * @api */ -goog.math.Coordinate.azimuth = function(a) { - return goog.math.angle(0, 0, a.x, a.y); +ol.control.Rotate.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + return; + } + var rotation = frameState.viewState.rotation; + if (rotation != this.rotation_) { + var transform = 'rotate(' + rotation + 'rad)'; + if (this.autoHide_) { + var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN); + if (!contains && rotation === 0) { + this.element.classList.add(ol.css.CLASS_HIDDEN); + } else if (contains && rotation !== 0) { + this.element.classList.remove(ol.css.CLASS_HIDDEN); + } + } + this.label_.style.msTransform = transform; + this.label_.style.webkitTransform = transform; + this.label_.style.transform = transform; + } + this.rotation_ = rotation; }; +goog.provide('ol.control.Zoom'); + +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); + /** - * Returns the squared distance between two coordinates. Squared distances can - * be used for comparisons when the actual value is not required. - * - * Performance note: eliminating the square root is an optimization often used - * in lower-level languages, but the speed difference is not nearly as - * pronounced in JavaScript (only a few percent.) + * @classdesc + * A control with 2 buttons, one for zoom in and one for zoom out. + * This control is one of the default controls of a map. To style this control + * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. * - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {number} The squared distance between {@code a} and {@code b}. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomOptions=} opt_options Zoom options. + * @api stable */ -goog.math.Coordinate.squaredDistance = function(a, b) { - var dx = a.x - b.x; - var dy = a.y - b.y; - return dx * dx + dy * dy; -}; +ol.control.Zoom = function(opt_options) { + var options = opt_options ? opt_options : {}; -/** - * Returns the difference between two coordinates as a new - * goog.math.Coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {!goog.math.Coordinate} A Coordinate representing the difference - * between {@code a} and {@code b}. - */ -goog.math.Coordinate.difference = function(a, b) { - return new goog.math.Coordinate(a.x - b.x, a.y - b.y); -}; + var className = options.className !== undefined ? options.className : 'ol-zoom'; + var delta = options.delta !== undefined ? options.delta : 1; -/** - * Returns the sum of two coordinates as a new goog.math.Coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two - * coordinates. - */ -goog.math.Coordinate.sum = function(a, b) { - return new goog.math.Coordinate(a.x + b.x, a.y + b.y); -}; + var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+'; + var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212'; + var zoomInTipLabel = options.zoomInTipLabel !== undefined ? + options.zoomInTipLabel : 'Zoom in'; + var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ? + options.zoomOutTipLabel : 'Zoom out'; -/** - * Rounds the x and y fields to the next larger integer values. - * @return {!goog.math.Coordinate} This coordinate with ceil'd fields. - */ -goog.math.Coordinate.prototype.ceil = function() { - this.x = Math.ceil(this.x); - this.y = Math.ceil(this.y); - return this; -}; + var inElement = document.createElement('button'); + inElement.className = className + '-in'; + inElement.setAttribute('type', 'button'); + inElement.title = zoomInTipLabel; + inElement.appendChild( + typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel + ); + ol.events.listen(inElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, delta)); + + var outElement = document.createElement('button'); + outElement.className = className + '-out'; + outElement.setAttribute('type', 'button'); + outElement.title = zoomOutTipLabel; + outElement.appendChild( + typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel + ); + + ol.events.listen(outElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, -delta)); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(inElement); + element.appendChild(outElement); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; -/** - * Rounds the x and y fields to the next smaller integer values. - * @return {!goog.math.Coordinate} This coordinate with floored fields. - */ -goog.math.Coordinate.prototype.floor = function() { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - return this; }; +ol.inherits(ol.control.Zoom, ol.control.Control); /** - * Rounds the x and y fields to the nearest integer values. - * @return {!goog.math.Coordinate} This coordinate with rounded fields. + * @param {number} delta Zoom delta. + * @param {Event} event The event to handle + * @private */ -goog.math.Coordinate.prototype.round = function() { - this.x = Math.round(this.x); - this.y = Math.round(this.y); - return this; +ol.control.Zoom.prototype.handleClick_ = function(delta, event) { + event.preventDefault(); + this.zoomByDelta_(delta); }; /** - * Translates this box by the given offsets. If a {@code goog.math.Coordinate} - * is given, then the x and y values are translated by the coordinate's x and y. - * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} - * respectively. - * @param {number|goog.math.Coordinate} tx The value to translate x by or the - * the coordinate to translate this coordinate by. - * @param {number=} opt_ty The value to translate y by. - * @return {!goog.math.Coordinate} This coordinate after translating. + * @param {number} delta Zoom delta. + * @private */ -goog.math.Coordinate.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.x += tx.x; - this.y += tx.y; - } else { - this.x += Number(tx); - if (goog.isNumber(opt_ty)) { - this.y += opt_ty; +ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentResolution = view.getResolution(); + if (currentResolution) { + if (this.duration_ > 0) { + map.beforeRender(ol.animation.zoom({ + resolution: currentResolution, + duration: this.duration_, + easing: ol.easing.easeOut + })); } + var newResolution = view.constrainResolution(currentResolution, delta); + view.setResolution(newResolution); } - return this; }; +goog.provide('ol.control'); -/** - * Scales this coordinate by the given scale factors. The x and y values are - * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} - * is not given, then {@code sx} is used for both x and y. - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Coordinate} This coordinate after scaling. - */ -goog.math.Coordinate.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.x *= sx; - this.y *= sy; - return this; -}; +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.Zoom'); /** - * Rotates this coordinate clockwise about the origin (or, optionally, the given - * center) by the given angle, in radians. - * @param {number} radians The angle by which to rotate this coordinate - * clockwise about the given center, in radians. - * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults - * to (0, 0) if not given. + * Set of controls included in maps by default. Unless configured otherwise, + * this returns a collection containing an instance of each of the following + * controls: + * * {@link ol.control.Zoom} + * * {@link ol.control.Rotate} + * * {@link ol.control.Attribution} + * + * @param {olx.control.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.} Controls. + * @api stable */ -goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) { - var center = opt_center || new goog.math.Coordinate(0, 0); +ol.control.defaults = function(opt_options) { - var x = this.x; - var y = this.y; - var cos = Math.cos(radians); - var sin = Math.sin(radians); + var options = opt_options ? opt_options : {}; - this.x = (x - center.x) * cos - (y - center.y) * sin + center.x; - this.y = (x - center.x) * sin + (y - center.y) * cos + center.y; -}; + var controls = new ol.Collection(); + var zoomControl = options.zoom !== undefined ? options.zoom : true; + if (zoomControl) { + controls.push(new ol.control.Zoom(options.zoomOptions)); + } -/** - * Rotates this coordinate clockwise about the origin (or, optionally, the given - * center) by the given angle, in degrees. - * @param {number} degrees The angle by which to rotate this coordinate - * clockwise about the given center, in degrees. - * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults - * to (0, 0) if not given. - */ -goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) { - this.rotateRadians(goog.math.toRadians(degrees), opt_center); -}; + var rotateControl = options.rotate !== undefined ? options.rotate : true; + if (rotateControl) { + controls.push(new ol.control.Rotate(options.rotateOptions)); + } -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + var attributionControl = options.attribution !== undefined ? + options.attribution : true; + if (attributionControl) { + controls.push(new ol.control.Attribution(options.attributionOptions)); + } -/** - * @fileoverview A utility class for representing two-dimensional sizes. - * @author brenneman@google.com (Shawn Brenneman) - */ + return controls; +}; -goog.provide('goog.math.Size'); +goog.provide('ol.control.FullScreen'); +goog.require('goog.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.control.Control'); +goog.require('ol.dom'); +goog.require('ol.css'); /** - * Class for representing sizes consisting of a width and height. Undefined - * width and height support is deprecated and results in compiler warning. - * @param {number} width Width. - * @param {number} height Height. - * @struct + * @classdesc + * Provides a button that when clicked fills up the full screen with the map. + * The full screen source element is by default the element containing the map viewport unless + * overriden by providing the `source` option. In which case, the dom + * element introduced using this parameter will be displayed in full screen. + * + * When in full screen mode, a close button is shown to exit full screen mode. + * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to + * toggle the map in full screen mode. + * + * * @constructor + * @extends {ol.control.Control} + * @param {olx.control.FullScreenOptions=} opt_options Options. + * @api stable */ -goog.math.Size = function(width, height) { +ol.control.FullScreen = function(opt_options) { + + var options = opt_options ? opt_options : {}; + /** - * Width - * @type {number} + * @private + * @type {string} */ - this.width = width; + this.cssClassName_ = options.className !== undefined ? options.className : + 'ol-full-screen'; + + var label = options.label !== undefined ? options.label : '\u2922'; /** - * Height - * @type {number} + * @private + * @type {Node} */ - this.height = height; -}; + this.labelNode_ = typeof label === 'string' ? + document.createTextNode(label) : label; + var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7'; -/** - * Compares sizes for equality. - * @param {goog.math.Size} a A Size. - * @param {goog.math.Size} b A Size. - * @return {boolean} True iff the sizes have equal widths and equal - * heights, or if both are null. - */ -goog.math.Size.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; - } - return a.width == b.width && a.height == b.height; -}; + /** + * @private + * @type {Node} + */ + this.labelActiveNode_ = typeof labelActive === 'string' ? + document.createTextNode(labelActive) : labelActive; + var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen'; + var button = document.createElement('button'); + button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen(); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.labelNode_); -/** - * @return {!goog.math.Size} A new copy of the Size. - */ -goog.math.Size.prototype.clone = function() { - return new goog.math.Size(this.width, this.height); -}; + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + + ' ' + ol.css.CLASS_CONTROL + ' ' + + (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : ''); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + ol.control.Control.call(this, { + element: element, + target: options.target + }); -if (goog.DEBUG) { /** - * Returns a nice string representing size. - * @return {string} In the form (50 x 73). - * @override + * @private + * @type {boolean} */ - goog.math.Size.prototype.toString = function() { - return '(' + this.width + ' x ' + this.height + ')'; - }; -} + this.keys_ = options.keys !== undefined ? options.keys : false; + /** + * @private + * @type {Element|string|undefined} + */ + this.source_ = options.source; -/** - * @return {number} The longer of the two dimensions in the size. - */ -goog.math.Size.prototype.getLongest = function() { - return Math.max(this.width, this.height); }; +ol.inherits(ol.control.FullScreen, ol.control.Control); /** - * @return {number} The shorter of the two dimensions in the size. + * @param {Event} event The event to handle + * @private */ -goog.math.Size.prototype.getShortest = function() { - return Math.min(this.width, this.height); +ol.control.FullScreen.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleFullScreen_(); }; /** - * @return {number} The area of the size (width * height). + * @private */ -goog.math.Size.prototype.area = function() { - return this.width * this.height; +ol.control.FullScreen.prototype.handleFullScreen_ = function() { + if (!ol.control.FullScreen.isFullScreenSupported()) { + return; + } + var map = this.getMap(); + if (!map) { + return; + } + if (ol.control.FullScreen.isFullScreen()) { + ol.control.FullScreen.exitFullScreen(); + } else { + var element; + if (this.source_) { + element = typeof this.source_ === 'string' ? + document.getElementById(this.source_) : + this.source_; + } else { + element = map.getTargetElement(); + } + goog.asserts.assert(element, 'element should be defined'); + if (this.keys_) { + ol.control.FullScreen.requestFullScreenWithKeys(element); + + } else { + ol.control.FullScreen.requestFullScreen(element); + } + } }; /** - * @return {number} The perimeter of the size (width + height) * 2. + * @private */ -goog.math.Size.prototype.perimeter = function() { - return (this.width + this.height) * 2; +ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { + var button = this.element.firstElementChild; + var map = this.getMap(); + if (ol.control.FullScreen.isFullScreen()) { + button.className = this.cssClassName_ + '-true'; + ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_); + } else { + button.className = this.cssClassName_ + '-false'; + ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_); + } + if (map) { + map.updateSize(); + } }; /** - * @return {number} The ratio of the size's width to its height. + * @inheritDoc + * @api stable */ -goog.math.Size.prototype.aspectRatio = function() { - return this.width / this.height; +ol.control.FullScreen.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + this.listenerKeys.push(ol.events.listen(ol.global.document, + ol.control.FullScreen.getChangeType_(), + this.handleFullScreenChange_, this) + ); + } }; - /** - * @return {boolean} True if the size has zero area, false if both dimensions - * are non-zero numbers. + * @return {boolean} Fullscreen is supported by the current platform. */ -goog.math.Size.prototype.isEmpty = function() { - return !this.area(); +ol.control.FullScreen.isFullScreenSupported = function() { + var body = document.body; + return !!( + body.webkitRequestFullscreen || + (body.mozRequestFullScreen && document.mozFullScreenEnabled) || + (body.msRequestFullscreen && document.msFullscreenEnabled) || + (body.requestFullscreen && document.fullscreenEnabled) + ); }; - /** - * Clamps the width and height parameters upward to integer values. - * @return {!goog.math.Size} This size with ceil'd components. + * @return {boolean} Element is currently in fullscreen. */ -goog.math.Size.prototype.ceil = function() { - this.width = Math.ceil(this.width); - this.height = Math.ceil(this.height); - return this; +ol.control.FullScreen.isFullScreen = function() { + return !!( + document.webkitIsFullScreen || document.mozFullScreen || + document.msFullscreenElement || document.fullscreenElement + ); }; - /** - * @param {!goog.math.Size} target The target size. - * @return {boolean} True if this Size is the same size or smaller than the - * target size in both dimensions. + * Request to fullscreen an element. + * @param {Node} element Element to request fullscreen */ -goog.math.Size.prototype.fitsInside = function(target) { - return this.width <= target.width && this.height <= target.height; +ol.control.FullScreen.requestFullScreen = function(element) { + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } }; - /** - * Clamps the width and height parameters downward to integer values. - * @return {!goog.math.Size} This size with floored components. + * Request to fullscreen an element with keyboard input. + * @param {Node} element Element to request fullscreen */ -goog.math.Size.prototype.floor = function() { - this.width = Math.floor(this.width); - this.height = Math.floor(this.height); - return this; +ol.control.FullScreen.requestFullScreenWithKeys = function(element) { + if (element.mozRequestFullScreenWithKeys) { + element.mozRequestFullScreenWithKeys(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + ol.control.FullScreen.requestFullScreen(element); + } }; - /** - * Rounds the width and height parameters to integer values. - * @return {!goog.math.Size} This size with rounded components. + * Exit fullscreen. */ -goog.math.Size.prototype.round = function() { - this.width = Math.round(this.width); - this.height = Math.round(this.height); - return this; +ol.control.FullScreen.exitFullScreen = function() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } }; - /** - * Scales this size by the given scale factors. The width and height are scaled - * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not - * given, then {@code sx} is used for both the width and height. - * @param {number} sx The scale factor to use for the width. - * @param {number=} opt_sy The scale factor to use for the height. - * @return {!goog.math.Size} This Size object after scaling. + * @return {string} Change type. + * @private */ -goog.math.Size.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.width *= sx; - this.height *= sy; - return this; -}; +ol.control.FullScreen.getChangeType_ = (function() { + var changeType; + return function() { + if (!changeType) { + var body = document.body; + if (body.webkitRequestFullscreen) { + changeType = 'webkitfullscreenchange'; + } else if (body.mozRequestFullScreen) { + changeType = 'mozfullscreenchange'; + } else if (body.msRequestFullscreen) { + changeType = 'MSFullscreenChange'; + } else if (body.requestFullscreen) { + changeType = 'fullscreenchange'; + } + } + return changeType; + }; +})(); +// FIXME should listen on appropriate pane, once it is defined -/** - * Uniformly scales the size to perfectly cover the dimensions of a given size. - * If the size is already larger than the target, it will be scaled down to the - * minimum size at which it still covers the entire target. The original aspect - * ratio will be preserved. - * - * This function assumes that both Sizes contain strictly positive dimensions. - * @param {!goog.math.Size} target The target size. - * @return {!goog.math.Size} This Size object, after optional scaling. - */ -goog.math.Size.prototype.scaleToCover = function(target) { - var s = this.aspectRatio() <= target.aspectRatio() ? - target.width / this.width : - target.height / this.height; +goog.provide('ol.control.MousePosition'); - return this.scale(s); -}; +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.Object'); +goog.require('ol.control.Control'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); /** - * Uniformly scales the size to fit inside the dimensions of a given size. The - * original aspect ratio will be preserved. - * - * This function assumes that both Sizes contain strictly positive dimensions. - * @param {!goog.math.Size} target The target size. - * @return {!goog.math.Size} This Size object, after optional scaling. + * @enum {string} */ -goog.math.Size.prototype.scaleToFit = function(target) { - var s = this.aspectRatio() > target.aspectRatio() ? - target.width / this.width : - target.height / this.height; - - return this.scale(s); +ol.control.MousePositionProperty = { + PROJECTION: 'projection', + COORDINATE_FORMAT: 'coordinateFormat' }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for manipulating the browser's Document Object Model - * Inspiration taken *heavily* from mochikit (http://mochikit.com/). - * - * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer - * to a different document object. This is useful if you are working with - * frames or multiple windows. + * @classdesc + * A control to show the 2D coordinates of the mouse cursor. By default, these + * are in the view projection, but can be in any supported projection. + * By default the control is shown in the top right corner of the map, but this + * can be changed by using the css selector `.ol-mouse-position`. * - * @author arv@google.com (Erik Arvidsson) + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.MousePositionOptions=} opt_options Mouse position + * options. + * @api stable */ +ol.control.MousePosition = function(opt_options) { + var options = opt_options ? opt_options : {}; -// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem -// is that getTextContent should mimic the DOM3 textContent. We should add a -// getInnerText (or getText) which tries to return the visible text, innerText. - + var element = document.createElement('DIV'); + element.className = options.className !== undefined ? options.className : 'ol-mouse-position'; -goog.provide('goog.dom'); -goog.provide('goog.dom.Appendable'); -goog.provide('goog.dom.DomHelper'); + var render = options.render ? + options.render : ol.control.MousePosition.render; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.BrowserFeature'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.safe'); -goog.require('goog.html.SafeHtml'); -goog.require('goog.html.legacyconversions'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Size'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.string.Unicode'); -goog.require('goog.userAgent'); + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + ol.events.listen(this, + ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION), + this.handleProjectionChanged_, this); -/** - * @define {boolean} Whether we know at compile time that the browser is in - * quirks mode. - */ -goog.define('goog.dom.ASSUME_QUIRKS_MODE', false); + if (options.coordinateFormat) { + this.setCoordinateFormat(options.coordinateFormat); + } + if (options.projection) { + this.setProjection(ol.proj.get(options.projection)); + } + /** + * @private + * @type {string} + */ + this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : ''; -/** - * @define {boolean} Whether we know at compile time that the browser is in - * standards compliance mode. - */ -goog.define('goog.dom.ASSUME_STANDARDS_MODE', false); + /** + * @private + * @type {string} + */ + this.renderedHTML_ = element.innerHTML; + /** + * @private + * @type {ol.proj.Projection} + */ + this.mapProjection_ = null; -/** - * Whether we know the compatibility mode at compile time. - * @type {boolean} - * @private - */ -goog.dom.COMPAT_MODE_KNOWN_ = - goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; - - -/** - * Gets the DomHelper object for the document where the element resides. - * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this - * element. - * @return {!goog.dom.DomHelper} The DomHelper. - */ -goog.dom.getDomHelper = function(opt_element) { - return opt_element ? - new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : - (goog.dom.defaultDomHelper_ || - (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper())); -}; - - -/** - * Cached default DOM helper. - * @type {!goog.dom.DomHelper|undefined} - * @private - */ -goog.dom.defaultDomHelper_; + /** + * @private + * @type {?ol.TransformFunction} + */ + this.transform_ = null; + /** + * @private + * @type {ol.Pixel} + */ + this.lastMouseMovePixel_ = null; -/** - * Gets the document object being used by the dom library. - * @return {!Document} Document object. - */ -goog.dom.getDocument = function() { - return document; }; +ol.inherits(ol.control.MousePosition, ol.control.Control); /** - * Gets an element from the current document by element id. - * - * If an Element is passed in, it is returned. - * - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. + * Update the mouseposition element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.MousePosition} + * @api */ -goog.dom.getElement = function(element) { - return goog.dom.getElementHelper_(document, element); +ol.control.MousePosition.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + this.mapProjection_ = null; + } else { + if (this.mapProjection_ != frameState.viewState.projection) { + this.mapProjection_ = frameState.viewState.projection; + this.transform_ = null; + } + } + this.updateHTML_(this.lastMouseMovePixel_); }; /** - * Gets an element by id from the given document (if present). - * If an element is given, it is returned. - * @param {!Document} doc - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The resulting element. * @private */ -goog.dom.getElementHelper_ = function(doc, element) { - return goog.isString(element) ? doc.getElementById(element) : element; +ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { + this.transform_ = null; }; /** - * Gets an element by id, asserting that the element is found. - * - * This is used when an element is expected to exist, and should fail with - * an assertion error if it does not (if assertions are enabled). - * - * @param {string} id Element ID. - * @return {!Element} The element with the given ID, if it exists. + * Return the coordinate format type used to render the current position or + * undefined. + * @return {ol.CoordinateFormatType|undefined} The format to render the current + * position in. + * @observable + * @api stable */ -goog.dom.getRequiredElement = function(id) { - return goog.dom.getRequiredElementHelper_(document, id); +ol.control.MousePosition.prototype.getCoordinateFormat = function() { + return /** @type {ol.CoordinateFormatType|undefined} */ ( + this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT)); }; /** - * Helper function for getRequiredElementHelper functions, both static and - * on DomHelper. Asserts the element with the given id exists. - * @param {!Document} doc - * @param {string} id - * @return {!Element} The element with the given ID, if it exists. - * @private + * Return the projection that is used to report the mouse position. + * @return {ol.proj.Projection|undefined} The projection to report mouse + * position in. + * @observable + * @api stable */ -goog.dom.getRequiredElementHelper_ = function(doc, id) { - // To prevent users passing in Elements as is permitted in getElement(). - goog.asserts.assertString(id); - var element = goog.dom.getElementHelper_(doc, id); - element = - goog.asserts.assertElement(element, 'No element found with id: ' + id); - return element; +ol.control.MousePosition.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.control.MousePositionProperty.PROJECTION)); }; /** - * Alias for getElement. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. - * @deprecated Use {@link goog.dom.getElement} instead. - */ -goog.dom.$ = goog.dom.getElement; - - -/** - * Looks up elements by both tag and class name, using browser native functions - * ({@code querySelectorAll}, {@code getElementsByTagName} or - * {@code getElementsByClassName}) where possible. This function - * is a useful, if limited, way of collecting a list of DOM elements - * with certain characteristics. {@code goog.dom.query} offers a - * more powerful and general solution which allows matching on CSS3 - * selector expressions, but at increased cost in code size. If all you - * need is particular tags belonging to a single class, this function - * is fast and sleek. - * - * Note that tag names are case sensitive in the SVG namespace, and this - * function converts opt_tag to uppercase for comparisons. For queries in the - * SVG namespace you should use querySelector or querySelectorAll instead. - * https://bugzilla.mozilla.org/show_bug.cgi?id=963870 - * https://bugs.webkit.org/show_bug.cgi?id=83438 - * - * @see {goog.dom.query} - * - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return {!IArrayLike} Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * @param {Event} event Browser event. + * @protected */ -goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { - return goog.dom.getElementsByTagNameAndClass_( - document, opt_tag, opt_class, opt_el); +ol.control.MousePosition.prototype.handleMouseMove = function(event) { + var map = this.getMap(); + this.lastMouseMovePixel_ = map.getEventPixel(event); + this.updateHTML_(this.lastMouseMovePixel_); }; /** - * Returns a static, array-like list of the elements with the provided - * className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return {!IArrayLike} The items found with the class name provided. + * @param {Event} event Browser event. + * @protected */ -goog.dom.getElementsByClass = function(className, opt_el) { - var parent = opt_el || document; - if (goog.dom.canUseQuerySelector_(parent)) { - return parent.querySelectorAll('.' + className); - } - return goog.dom.getElementsByTagNameAndClass_( - document, '*', className, opt_el); +ol.control.MousePosition.prototype.handleMouseOut = function(event) { + this.updateHTML_(null); + this.lastMouseMovePixel_ = null; }; /** - * Returns the first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {Element|Document=} opt_el Optional element to look in. - * @return {Element} The first item with the class name provided. + * @inheritDoc + * @api stable */ -goog.dom.getElementByClass = function(className, opt_el) { - var parent = opt_el || document; - var retVal = null; - if (parent.getElementsByClassName) { - retVal = parent.getElementsByClassName(className)[0]; - } else if (goog.dom.canUseQuerySelector_(parent)) { - retVal = parent.querySelector('.' + className); - } else { - retVal = goog.dom.getElementsByTagNameAndClass_( - document, '*', className, opt_el)[0]; +ol.control.MousePosition.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + var viewport = map.getViewport(); + this.listenerKeys.push( + ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE, + this.handleMouseMove, this), + ol.events.listen(viewport, ol.events.EventType.MOUSEOUT, + this.handleMouseOut, this) + ); } - return retVal || null; }; /** - * Ensures an element with the given className exists, and then returns the - * first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {!Element|!Document=} opt_root Optional element or document to look - * in. - * @return {!Element} The first item with the class name provided. - * @throws {goog.asserts.AssertionError} Thrown if no element is found. + * Set the coordinate format type used to render the current position. + * @param {ol.CoordinateFormatType} format The format to render the current + * position in. + * @observable + * @api stable */ -goog.dom.getRequiredElementByClass = function(className, opt_root) { - var retValue = goog.dom.getElementByClass(className, opt_root); - return goog.asserts.assert( - retValue, 'No element found with className: ' + className); +ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { + this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format); }; /** - * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and - * fast W3C Selectors API. - * @param {!(Element|Document)} parent The parent document object. - * @return {boolean} whether or not we can use parent.querySelector* APIs. - * @private + * Set the projection that is used to report the mouse position. + * @param {ol.proj.Projection} projection The projection to report mouse + * position in. + * @observable + * @api stable */ -goog.dom.canUseQuerySelector_ = function(parent) { - return !!(parent.querySelectorAll && parent.querySelector); +ol.control.MousePosition.prototype.setProjection = function(projection) { + this.set(ol.control.MousePositionProperty.PROJECTION, projection); }; /** - * Helper for {@code getElementsByTagNameAndClass}. - * @param {!Document} doc The document to get the elements in. - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return {!IArrayLike} Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * @param {?ol.Pixel} pixel Pixel. * @private */ -goog.dom.getElementsByTagNameAndClass_ = function( - doc, opt_tag, opt_class, opt_el) { - var parent = opt_el || doc; - var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : ''; - - if (goog.dom.canUseQuerySelector_(parent) && (tagName || opt_class)) { - var query = tagName + (opt_class ? '.' + opt_class : ''); - return parent.querySelectorAll(query); - } - - // Use the native getElementsByClassName if available, under the assumption - // that even when the tag name is specified, there will be fewer elements to - // filter through when going by class than by tag name - if (opt_class && parent.getElementsByClassName) { - var els = parent.getElementsByClassName(opt_class); - - if (tagName) { - var arrayLike = {}; - var len = 0; - - // Filter for specific tags if requested. - for (var i = 0, el; el = els[i]; i++) { - if (tagName == el.nodeName) { - arrayLike[len++] = el; - } +ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { + var html = this.undefinedHTML_; + if (pixel && this.mapProjection_) { + if (!this.transform_) { + var projection = this.getProjection(); + if (projection) { + this.transform_ = ol.proj.getTransformFromProjections( + this.mapProjection_, projection); + } else { + this.transform_ = ol.proj.identityTransform; } - arrayLike.length = len; - - return /** @type {!IArrayLike} */ (arrayLike); - } else { - return els; } - } - - var els = parent.getElementsByTagName(tagName || '*'); - - if (opt_class) { - var arrayLike = {}; - var len = 0; - for (var i = 0, el; el = els[i]; i++) { - var className = el.className; - // Check if className has a split function since SVG className does not. - if (typeof className.split == 'function' && - goog.array.contains(className.split(/\s+/), opt_class)) { - arrayLike[len++] = el; + var map = this.getMap(); + var coordinate = map.getCoordinateFromPixel(pixel); + if (coordinate) { + this.transform_(coordinate, coordinate); + var coordinateFormat = this.getCoordinateFormat(); + if (coordinateFormat) { + html = coordinateFormat(coordinate); + } else { + html = coordinate.toString(); } } - arrayLike.length = len; - return /** @type {!IArrayLike} */ (arrayLike); - } else { - return els; + } + if (!this.renderedHTML_ || html != this.renderedHTML_) { + this.element.innerHTML = html; + this.renderedHTML_ = html; } }; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Alias for {@code getElementsByTagNameAndClass}. - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {Element=} opt_el Optional element to look in. - * @return {!IArrayLike} Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). - * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead. + * @fileoverview A global registry for entry points into a program, + * so that they can be instrumented. Each module should register their + * entry points with this registry. Designed to be compiled out + * if no instrumentation is requested. + * + * Entry points may be registered before or after a call to + * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered + * later, the existing monitor will instrument the new entry point. + * + * @author nicksantos@google.com (Nick Santos) */ -goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; + +goog.provide('goog.debug.EntryPointMonitor'); +goog.provide('goog.debug.entryPointRegistry'); + +goog.require('goog.asserts'); + /** - * Sets multiple properties on a node. - * @param {Element} element DOM node to set properties on. - * @param {Object} properties Hash of property:value pairs. + * @interface */ -goog.dom.setProperties = function(element, properties) { - goog.object.forEach(properties, function(val, key) { - if (key == 'style') { - element.style.cssText = val; - } else if (key == 'class') { - element.className = val; - } else if (key == 'for') { - element.htmlFor = val; - } else if (goog.dom.DIRECT_ATTRIBUTE_MAP_.hasOwnProperty(key)) { - element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val); - } else if ( - goog.string.startsWith(key, 'aria-') || - goog.string.startsWith(key, 'data-')) { - element.setAttribute(key, val); - } else { - element[key] = val; - } - }); -}; +goog.debug.EntryPointMonitor = function() {}; /** - * Map of attributes that should be set using - * element.setAttribute(key, val) instead of element[key] = val. Used - * by goog.dom.setProperties. + * Instruments a function. * - * @private {!Object} - * @const + * @param {!Function} fn A function to instrument. + * @return {!Function} The instrumented function. */ -goog.dom.DIRECT_ATTRIBUTE_MAP_ = { - 'cellpadding': 'cellPadding', - 'cellspacing': 'cellSpacing', - 'colspan': 'colSpan', - 'frameborder': 'frameBorder', - 'height': 'height', - 'maxlength': 'maxLength', - 'nonce': 'nonce', - 'role': 'role', - 'rowspan': 'rowSpan', - 'type': 'type', - 'usemap': 'useMap', - 'valign': 'vAlign', - 'width': 'width' -}; +goog.debug.EntryPointMonitor.prototype.wrap; /** - * Gets the dimensions of the viewport. - * - * Gecko Standards mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of body element. - * - * docEl.clientHeight Height of viewport excluding scrollbar. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of document. - * - * Gecko Backwards compatible mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight Height of document. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of viewport excluding scrollbar. - * - * IE6/7 Standards mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Undefined. - * body.clientWidth Width of body element. - * - * docEl.clientHeight Height of viewport excluding scrollbar. - * win.innerHeight Undefined. - * body.clientHeight Height of document element. - * - * IE5 + IE6/7 Backwards compatible mode: - * docEl.clientWidth 0. - * win.innerWidth Undefined. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight 0. - * win.innerHeight Undefined. - * body.clientHeight Height of viewport excluding scrollbar. - * - * Opera 9 Standards and backwards compatible mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight Height of document. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of viewport excluding scrollbar. - * - * WebKit: - * Safari 2 - * docEl.clientHeight Same as scrollHeight. - * docEl.clientWidth Same as innerWidth. - * win.innerWidth Width of viewport excluding scrollbar. - * win.innerHeight Height of the viewport including scrollbar. - * frame.innerHeight Height of the viewport exluding scrollbar. + * Try to remove an instrumentation wrapper created by this monitor. + * If the function passed to unwrap is not a wrapper created by this + * monitor, then we will do nothing. * - * Safari 3 (tested in 522) + * Notice that some wrappers may not be unwrappable. For example, if other + * monitors have applied their own wrappers, then it will be impossible to + * unwrap them because their wrappers will have captured our wrapper. * - * docEl.clientWidth Width of viewport excluding scrollbar. - * docEl.clientHeight Height of viewport excluding scrollbar in strict mode. - * body.clientHeight Height of viewport excluding scrollbar in quirks mode. + * So it is important that entry points are unwrapped in the reverse + * order that they were wrapped. * - * @param {Window=} opt_window Optional window element to test. - * @return {!goog.math.Size} Object with values 'width' and 'height'. + * @param {!Function} fn A function to unwrap. + * @return {!Function} The unwrapped function, or {@code fn} if it was not + * a wrapped function created by this monitor. */ -goog.dom.getViewportSize = function(opt_window) { - // TODO(arv): This should not take an argument - return goog.dom.getViewportSize_(opt_window || window); -}; +goog.debug.EntryPointMonitor.prototype.unwrap; /** - * Helper for {@code getViewportSize}. - * @param {Window} win The window to get the view port size for. - * @return {!goog.math.Size} Object with values 'width' and 'height'. + * An array of entry point callbacks. + * @type {!Array} * @private */ -goog.dom.getViewportSize_ = function(win) { - var doc = win.document; - var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body; - return new goog.math.Size(el.clientWidth, el.clientHeight); -}; +goog.debug.entryPointRegistry.refList_ = []; /** - * Calculates the height of the document. - * - * @return {number} The height of the current document. + * Monitors that should wrap all the entry points. + * @type {!Array} + * @private */ -goog.dom.getDocumentHeight = function() { - return goog.dom.getDocumentHeight_(window); -}; +goog.debug.entryPointRegistry.monitors_ = []; + /** - * Calculates the height of the document of the given window. - * - * @param {!Window} win The window whose document height to retrieve. - * @return {number} The height of the document of the given window. + * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + * Checking this allows the compiler to optimize out the registrations. + * @type {boolean} + * @private */ -goog.dom.getDocumentHeightForWindow = function(win) { - return goog.dom.getDocumentHeight_(win); -}; +goog.debug.entryPointRegistry.monitorsMayExist_ = false; + /** - * Calculates the height of the document of the given window. + * Register an entry point with this module. * - * Function code copied from the opensocial gadget api: - * gadgets.window.adjustHeight(opt_height) + * The entry point will be instrumented when a monitor is passed to + * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the + * entry point is instrumented immediately. * - * @private - * @param {!Window} win The window whose document height to retrieve. - * @return {number} The height of the document of the given window. + * @param {function(!Function)} callback A callback function which is called + * with a transforming function to instrument the entry point. The callback + * is responsible for wrapping the relevant entry point with the + * transforming function. */ -goog.dom.getDocumentHeight_ = function(win) { - // NOTE(eae): This method will return the window size rather than the document - // size in webkit quirks mode. - var doc = win.document; - var height = 0; - - if (doc) { - // Calculating inner content height is hard and different between - // browsers rendering in Strict vs. Quirks mode. We use a combination of - // three properties within document.body and document.documentElement: - // - scrollHeight - // - offsetHeight - // - clientHeight - // These values differ significantly between browsers and rendering modes. - // But there are patterns. It just takes a lot of time and persistence - // to figure out. - - var body = doc.body; - var docEl = /** @type {!HTMLElement} */ (doc.documentElement); - if (!(docEl && body)) { - return 0; - } - - // Get the height of the viewport - var vh = goog.dom.getViewportSize_(win).height; - if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { - // In Strict mode: - // The inner content height is contained in either: - // document.documentElement.scrollHeight - // document.documentElement.offsetHeight - // Based on studying the values output by different browsers, - // use the value that's NOT equal to the viewport height found above. - height = - docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight; - } else { - // In Quirks mode: - // documentElement.clientHeight is equal to documentElement.offsetHeight - // except in IE. In most browsers, document.documentElement can be used - // to calculate the inner content height. - // However, in other browsers (e.g. IE), document.body must be used - // instead. How do we know which one to use? - // If document.documentElement.clientHeight does NOT equal - // document.documentElement.offsetHeight, then use document.body. - var sh = docEl.scrollHeight; - var oh = docEl.offsetHeight; - if (docEl.clientHeight != oh) { - sh = body.scrollHeight; - oh = body.offsetHeight; - } - - // Detect whether the inner content height is bigger or smaller - // than the bounding box (viewport). If bigger, take the larger - // value. If smaller, take the smaller value. - if (sh > vh) { - // Content is larger - height = sh > oh ? sh : oh; - } else { - // Content is smaller - height = sh < oh ? sh : oh; - } +goog.debug.entryPointRegistry.register = function(callback) { + // Don't use push(), so that this can be compiled out. + goog.debug.entryPointRegistry + .refList_[goog.debug.entryPointRegistry.refList_.length] = callback; + // If no one calls monitorAll, this can be compiled out. + if (goog.debug.entryPointRegistry.monitorsMayExist_) { + var monitors = goog.debug.entryPointRegistry.monitors_; + for (var i = 0; i < monitors.length; i++) { + callback(goog.bind(monitors[i].wrap, monitors[i])); } } - - return height; }; /** - * Gets the page scroll distance as a coordinate object. + * Configures a monitor to wrap all entry points. * - * @param {Window=} opt_window Optional window element to test. - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. - * @deprecated Use {@link goog.dom.getDocumentScroll} instead. - */ -goog.dom.getPageScroll = function(opt_window) { - var win = opt_window || goog.global || window; - return goog.dom.getDomHelper(win.document).getDocumentScroll(); -}; - - -/** - * Gets the document scroll distance as a coordinate object. + * Entry points that have already been registered are immediately wrapped by + * the monitor. When an entry point is registered in the future, it will also + * be wrapped by the monitor when it is registered. * - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor. */ -goog.dom.getDocumentScroll = function() { - return goog.dom.getDocumentScroll_(document); +goog.debug.entryPointRegistry.monitorAll = function(monitor) { + goog.debug.entryPointRegistry.monitorsMayExist_ = true; + var transformer = goog.bind(monitor.wrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + goog.debug.entryPointRegistry.monitors_.push(monitor); }; /** - * Helper for {@code getDocumentScroll}. + * Try to unmonitor all the entry points that have already been registered. If + * an entry point is registered in the future, it will not be wrapped by the + * monitor when it is registered. Note that this may fail if the entry points + * have additional wrapping. * - * @param {!Document} doc The document to get the scroll for. - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. - * @private + * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap + * the entry points. + * @throws {Error} If the monitor is not the most recently configured monitor. */ -goog.dom.getDocumentScroll_ = function(doc) { - var el = goog.dom.getDocumentScrollElement_(doc); - var win = goog.dom.getWindow_(doc); - if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') && - win.pageYOffset != el.scrollTop) { - // The keyboard on IE10 touch devices shifts the page using the pageYOffset - // without modifying scrollTop. For this case, we want the body scroll - // offsets. - return new goog.math.Coordinate(el.scrollLeft, el.scrollTop); +goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) { + var monitors = goog.debug.entryPointRegistry.monitors_; + goog.asserts.assert( + monitor == monitors[monitors.length - 1], + 'Only the most recent monitor can be unwrapped.'); + var transformer = goog.bind(monitor.unwrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); } - return new goog.math.Coordinate( - win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop); + monitors.length--; }; +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Gets the document scroll element. - * @return {!Element} Scrolling element. + * @fileoverview Defines the goog.dom.TagName enum. This enumerates + * all HTML tag names specified in either the the W3C HTML 4.01 index of + * elements or the HTML5 draft specification. + * + * References: + * http://www.w3.org/TR/html401/index/elements.html + * http://dev.w3.org/html5/spec/section-index.html + * */ -goog.dom.getDocumentScrollElement = function() { - return goog.dom.getDocumentScrollElement_(document); -}; +goog.provide('goog.dom.TagName'); /** - * Helper for {@code getDocumentScrollElement}. - * @param {!Document} doc The document to get the scroll element for. - * @return {!Element} Scrolling element. - * @private + * Enum of all html tag names specified by the W3C HTML4.01 and HTML5 + * specifications. + * @enum {string} */ -goog.dom.getDocumentScrollElement_ = function(doc) { - // Old WebKit needs body.scrollLeft in both quirks mode and strict mode. We - // also default to the documentElement if the document does not have a body - // (e.g. a SVG document). - // Uses http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement to - // avoid trying to guess about browser behavior from the UA string. - if (doc.scrollingElement) { - return doc.scrollingElement; - } - if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) { - return doc.documentElement; - } - return doc.body || doc.documentElement; +goog.dom.TagName = { + A: 'A', + ABBR: 'ABBR', + ACRONYM: 'ACRONYM', + ADDRESS: 'ADDRESS', + APPLET: 'APPLET', + AREA: 'AREA', + ARTICLE: 'ARTICLE', + ASIDE: 'ASIDE', + AUDIO: 'AUDIO', + B: 'B', + BASE: 'BASE', + BASEFONT: 'BASEFONT', + BDI: 'BDI', + BDO: 'BDO', + BIG: 'BIG', + BLOCKQUOTE: 'BLOCKQUOTE', + BODY: 'BODY', + BR: 'BR', + BUTTON: 'BUTTON', + CANVAS: 'CANVAS', + CAPTION: 'CAPTION', + CENTER: 'CENTER', + CITE: 'CITE', + CODE: 'CODE', + COL: 'COL', + COLGROUP: 'COLGROUP', + COMMAND: 'COMMAND', + DATA: 'DATA', + DATALIST: 'DATALIST', + DD: 'DD', + DEL: 'DEL', + DETAILS: 'DETAILS', + DFN: 'DFN', + DIALOG: 'DIALOG', + DIR: 'DIR', + DIV: 'DIV', + DL: 'DL', + DT: 'DT', + EM: 'EM', + EMBED: 'EMBED', + FIELDSET: 'FIELDSET', + FIGCAPTION: 'FIGCAPTION', + FIGURE: 'FIGURE', + FONT: 'FONT', + FOOTER: 'FOOTER', + FORM: 'FORM', + FRAME: 'FRAME', + FRAMESET: 'FRAMESET', + H1: 'H1', + H2: 'H2', + H3: 'H3', + H4: 'H4', + H5: 'H5', + H6: 'H6', + HEAD: 'HEAD', + HEADER: 'HEADER', + HGROUP: 'HGROUP', + HR: 'HR', + HTML: 'HTML', + I: 'I', + IFRAME: 'IFRAME', + IMG: 'IMG', + INPUT: 'INPUT', + INS: 'INS', + ISINDEX: 'ISINDEX', + KBD: 'KBD', + KEYGEN: 'KEYGEN', + LABEL: 'LABEL', + LEGEND: 'LEGEND', + LI: 'LI', + LINK: 'LINK', + MAP: 'MAP', + MARK: 'MARK', + MATH: 'MATH', + MENU: 'MENU', + META: 'META', + METER: 'METER', + NAV: 'NAV', + NOFRAMES: 'NOFRAMES', + NOSCRIPT: 'NOSCRIPT', + OBJECT: 'OBJECT', + OL: 'OL', + OPTGROUP: 'OPTGROUP', + OPTION: 'OPTION', + OUTPUT: 'OUTPUT', + P: 'P', + PARAM: 'PARAM', + PRE: 'PRE', + PROGRESS: 'PROGRESS', + Q: 'Q', + RP: 'RP', + RT: 'RT', + RUBY: 'RUBY', + S: 'S', + SAMP: 'SAMP', + SCRIPT: 'SCRIPT', + SECTION: 'SECTION', + SELECT: 'SELECT', + SMALL: 'SMALL', + SOURCE: 'SOURCE', + SPAN: 'SPAN', + STRIKE: 'STRIKE', + STRONG: 'STRONG', + STYLE: 'STYLE', + SUB: 'SUB', + SUMMARY: 'SUMMARY', + SUP: 'SUP', + SVG: 'SVG', + TABLE: 'TABLE', + TBODY: 'TBODY', + TD: 'TD', + TEMPLATE: 'TEMPLATE', + TEXTAREA: 'TEXTAREA', + TFOOT: 'TFOOT', + TH: 'TH', + THEAD: 'THEAD', + TIME: 'TIME', + TITLE: 'TITLE', + TR: 'TR', + TRACK: 'TRACK', + TT: 'TT', + U: 'U', + UL: 'UL', + VAR: 'VAR', + VIDEO: 'VIDEO', + WBR: 'WBR' }; +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Gets the window object associated with the given document. + * @fileoverview Utilities for creating functions. Loosely inspired by the + * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8. * - * @param {Document=} opt_doc Document object to get window for. - * @return {!Window} The window associated with the given document. + * @author nicksantos@google.com (Nick Santos) */ -goog.dom.getWindow = function(opt_doc) { - // TODO(arv): This should not take an argument. - return opt_doc ? goog.dom.getWindow_(opt_doc) : window; -}; + + +goog.provide('goog.functions'); /** - * Helper for {@code getWindow}. - * - * @param {!Document} doc Document object to get window for. - * @return {!Window} The window associated with the given document. - * @private + * Creates a function that always returns the same value. + * @param {T} retValue The value to return. + * @return {function():T} The new function. + * @template T */ -goog.dom.getWindow_ = function(doc) { - return doc.parentWindow || doc.defaultView; +goog.functions.constant = function(retValue) { + return function() { return retValue; }; }; /** - * Returns a dom node with a set of attributes. This function accepts varargs - * for subsequent nodes to be added. Subsequent nodes will be added to the - * first node as childNodes. - * - * So: - * createDom('div', null, createDom('p'), createDom('p')); - * would return a div with two child paragraphs - * - * @param {string} tagName Tag to create. - * @param {(Object|Array|string)=} opt_attributes If object, then a map - * of name-value pairs for attributes. If a string, then this is the - * className of the new element. If an array, the elements will be joined - * together as the className of the new element. - * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array or NodeList, - * its elements will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. + * Always returns false. + * @type {function(...): boolean} */ -goog.dom.createDom = function(tagName, opt_attributes, var_args) { - return goog.dom.createDom_(document, arguments); -}; +goog.functions.FALSE = goog.functions.constant(false); /** - * Helper for {@code createDom}. - * @param {!Document} doc The document to create the DOM in. - * @param {!Arguments} args Argument object passed from the callers. See - * {@code goog.dom.createDom} for details. - * @return {!Element} Reference to a DOM node. - * @private + * Always returns true. + * @type {function(...): boolean} */ -goog.dom.createDom_ = function(doc, args) { - var tagName = args[0]; - var attributes = args[1]; - - // Internet Explorer is dumb: - // name: https://msdn.microsoft.com/en-us/library/ms534184(v=vs.85).aspx - // type: https://msdn.microsoft.com/en-us/library/ms534700(v=vs.85).aspx - // Also does not allow setting of 'type' attribute on 'input' or 'button'. - if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && - (attributes.name || attributes.type)) { - var tagNameArr = ['<', tagName]; - if (attributes.name) { - tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), '"'); - } - if (attributes.type) { - tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), '"'); +goog.functions.TRUE = goog.functions.constant(true); - // Clone attributes map to remove 'type' without mutating the input. - var clone = {}; - goog.object.extend(clone, attributes); - // JSCompiler can't see how goog.object.extend added this property, - // because it was essentially added by reflection. - // So it needs to be quoted. - delete clone['type']; +/** + * Always returns NULL. + * @type {function(...): null} + */ +goog.functions.NULL = goog.functions.constant(null); - attributes = clone; - } - tagNameArr.push('>'); - tagName = tagNameArr.join(''); - } - var element = doc.createElement(tagName); - - if (attributes) { - if (goog.isString(attributes)) { - element.className = attributes; - } else if (goog.isArray(attributes)) { - element.className = attributes.join(' '); - } else { - goog.dom.setProperties(element, attributes); - } - } - - if (args.length > 2) { - goog.dom.append_(doc, element, args, 2); - } - - return element; -}; - - -/** - * Appends a node with text or other nodes. - * @param {!Document} doc The document to create new nodes in. - * @param {!Node} parent The node to append nodes to. - * @param {!Arguments} args The values to add. See {@code goog.dom.append}. - * @param {number} startIndex The index of the array to start from. - * @private - */ -goog.dom.append_ = function(doc, parent, args, startIndex) { - function childHandler(child) { - // TODO(user): More coercion, ala MochiKit? - if (child) { - parent.appendChild( - goog.isString(child) ? doc.createTextNode(child) : child); - } - } - - for (var i = startIndex; i < args.length; i++) { - var arg = args[i]; - // TODO(attila): Fix isArrayLike to return false for a text node. - if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) { - // If the argument is a node list, not a real array, use a clone, - // because forEach can't be used to mutate a NodeList. - goog.array.forEach( - goog.dom.isNodeList(arg) ? goog.array.toArray(arg) : arg, - childHandler); - } else { - childHandler(arg); - } - } -}; - - -/** - * Alias for {@code createDom}. - * @param {string} tagName Tag to create. - * @param {(string|Object)=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array, its - * children will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. - * @deprecated Use {@link goog.dom.createDom} instead. - */ -goog.dom.$dom = goog.dom.createDom; +/** + * A simple function that returns the first argument of whatever is passed + * into it. + * @param {T=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {T} The first argument passed in, or undefined if nothing was passed. + * @template T + */ +goog.functions.identity = function(opt_returnValue, var_args) { + return opt_returnValue; +}; /** - * Creates a new element. - * @param {string} name Tag name. - * @return {!Element} The new element. + * Creates a function that always throws an error with the given message. + * @param {string} message The error message. + * @return {!Function} The error-throwing function. */ -goog.dom.createElement = function(name) { - return document.createElement(name); +goog.functions.error = function(message) { + return function() { throw Error(message); }; }; /** - * Creates a new text node. - * @param {number|string} content Content. - * @return {!Text} The new text node. + * Creates a function that throws the given object. + * @param {*} err An object to be thrown. + * @return {!Function} The error-throwing function. */ -goog.dom.createTextNode = function(content) { - return document.createTextNode(String(content)); +goog.functions.fail = function(err) { + return function() { throw err; }; }; /** - * Create a table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean=} opt_fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!Element} The created table. + * Given a function, create a function that keeps opt_numArgs arguments and + * silently discards all additional arguments. + * @param {Function} f The original function. + * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0. + * @return {!Function} A version of f that only keeps the first opt_numArgs + * arguments. */ -goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { - // TODO(user): Return HTMLTableElement, also in prototype function. - // Callers need to be updated to e.g. not assign numbers to table.cellSpacing. - return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp); +goog.functions.lock = function(f, opt_numArgs) { + opt_numArgs = opt_numArgs || 0; + return function() { + return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs)); + }; }; /** - * Create a table. - * @param {!Document} doc Document object to use to create the table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean} fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!HTMLTableElement} The created table. - * @private + * Creates a function that returns its nth argument. + * @param {number} n The position of the return argument. + * @return {!Function} A new function. */ -goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { - var table = /** @type {!HTMLTableElement} */ - (doc.createElement(goog.dom.TagName.TABLE)); - var tbody = table.appendChild(doc.createElement(goog.dom.TagName.TBODY)); - for (var i = 0; i < rows; i++) { - var tr = doc.createElement(goog.dom.TagName.TR); - for (var j = 0; j < columns; j++) { - var td = doc.createElement(goog.dom.TagName.TD); - // IE <= 9 will create a text node if we set text content to the empty - // string, so we avoid doing it unless necessary. This ensures that the - // same DOM tree is returned on all browsers. - if (fillWithNbsp) { - goog.dom.setTextContent(td, goog.string.Unicode.NBSP); - } - tr.appendChild(td); - } - tbody.appendChild(tr); - } - return table; +goog.functions.nth = function(n) { + return function() { return arguments[n]; }; }; /** - * Converts HTML markup into a node. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. + * Like goog.partial(), except that arguments are added after arguments to the + * returned function. + * + * Usage: + * function f(arg1, arg2, arg3, arg4) { ... } + * var g = goog.functions.partialRight(f, arg3, arg4); + * g(arg1, arg2); + * + * @param {!Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn + * at the end. + * @return {!Function} A partially-applied form of the function goog.partial() + * was invoked as a method of. */ -goog.dom.safeHtmlToNode = function(html) { - return goog.dom.safeHtmlToNode_(document, html); +goog.functions.partialRight = function(fn, var_args) { + var rightArgs = Array.prototype.slice.call(arguments, 1); + return function() { + var newArgs = Array.prototype.slice.call(arguments); + newArgs.push.apply(newArgs, rightArgs); + return fn.apply(this, newArgs); + }; }; /** - * Helper for {@code safeHtmlToNode}. - * @param {!Document} doc The document. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. - * @private + * Given a function, create a new function that swallows its return value + * and replaces it with a new one. + * @param {Function} f A function. + * @param {T} retValue A new return value. + * @return {function(...?):T} A new function. + * @template T */ -goog.dom.safeHtmlToNode_ = function(doc, html) { - var tempDiv = doc.createElement(goog.dom.TagName.DIV); - if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { - goog.dom.safe.setInnerHtml( - tempDiv, goog.html.SafeHtml.concat(goog.html.SafeHtml.BR, html)); - tempDiv.removeChild(tempDiv.firstChild); - } else { - goog.dom.safe.setInnerHtml(tempDiv, html); - } - return goog.dom.childrenToNode_(doc, tempDiv); +goog.functions.withReturnValue = function(f, retValue) { + return goog.functions.sequence(f, goog.functions.constant(retValue)); }; /** - * Converts an HTML string into a document fragment. The string must be - * sanitized in order to avoid cross-site scripting. For example - * {@code goog.dom.htmlToDocumentFragment('<img src=x onerror=alert(0)>')} - * triggers an alert in all browsers, even if the returned document fragment - * is thrown away immediately. + * Creates a function that returns whether its argument equals the given value. * - * NOTE: This method doesn't work if your htmlString contains elements that - * can't be contained in a
. For example, . + * Example: + * var key = goog.object.findKey(obj, goog.functions.equalTo('needle')); * - * @param {string} htmlString The HTML string to convert. - * @return {!Node} The resulting document fragment. - * @deprecated Use {@link goog.dom.safeHtmlToNode} instead. + * @param {*} value The value to compare to. + * @param {boolean=} opt_useLooseComparison Whether to use a loose (==) + * comparison rather than a strict (===) one. Defaults to false. + * @return {function(*):boolean} The new function. */ -goog.dom.htmlToDocumentFragment = function(htmlString) { - return goog.dom.safeHtmlToNode_(document, - // For now, we are blindly trusting that the HTML is safe. - goog.html.legacyconversions.safeHtmlFromString(htmlString)); +goog.functions.equalTo = function(value, opt_useLooseComparison) { + return function(other) { + return opt_useLooseComparison ? (value == other) : (value === other); + }; }; /** - * Helper for {@code safeHtmlToNode_}. - * @param {!Document} doc The document. - * @param {!Node} tempDiv The input node. - * @return {!Node} The resulting node. - * @private + * Creates the composition of the functions passed in. + * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)). + * @param {function(...?):T} fn The final function. + * @param {...Function} var_args A list of functions. + * @return {function(...?):T} The composition of all inputs. + * @template T */ -goog.dom.childrenToNode_ = function(doc, tempDiv) { - if (tempDiv.childNodes.length == 1) { - return tempDiv.removeChild(tempDiv.firstChild); - } else { - var fragment = doc.createDocumentFragment(); - while (tempDiv.firstChild) { - fragment.appendChild(tempDiv.firstChild); +goog.functions.compose = function(fn, var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + if (length) { + result = functions[length - 1].apply(this, arguments); } - return fragment; - } -}; - -/** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @return {boolean} True if in CSS1-compatible mode. - */ -goog.dom.isCss1CompatMode = function() { - return goog.dom.isCss1CompatMode_(document); + for (var i = length - 2; i >= 0; i--) { + result = functions[i].call(this, result); + } + return result; + }; }; /** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @param {!Document} doc The document to check. - * @return {boolean} True if in CSS1-compatible mode. - * @private + * Creates a function that calls the functions passed in in sequence, and + * returns the value of the last function. For example, + * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x). + * @param {...Function} var_args A list of functions. + * @return {!Function} A function that calls all inputs in sequence. */ -goog.dom.isCss1CompatMode_ = function(doc) { - if (goog.dom.COMPAT_MODE_KNOWN_) { - return goog.dom.ASSUME_STANDARDS_MODE; - } - - return doc.compatMode == 'CSS1Compat'; +goog.functions.sequence = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; }; /** - * Determines if the given node can contain children, intended to be used for - * HTML generation. - * - * IE natively supports node.canHaveChildren but has inconsistent behavior. - * Prior to IE8 the base tag allows children and in IE9 all nodes return true - * for canHaveChildren. - * - * In practice all non-IE browsers allow you to add children to any node, but - * the behavior is inconsistent: - * - *
- *   var a = document.createElement(goog.dom.TagName.BR);
- *   a.appendChild(document.createTextNode('foo'));
- *   a.appendChild(document.createTextNode('bar'));
- *   console.log(a.childNodes.length);  // 2
- *   console.log(a.innerHTML);  // Chrome: "", IE9: "foobar", FF3.5: "foobar"
- * 
- * - * For more information, see: - * http://dev.w3.org/html5/markup/syntax.html#syntax-elements - * - * TODO(user): Rename shouldAllowChildren() ? - * - * @param {Node} node The node to check. - * @return {boolean} Whether the node can contain children. + * Creates a function that returns true if each of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns false. + * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ANDs its component + * functions. */ -goog.dom.canHaveChildren = function(node) { - if (node.nodeType != goog.dom.NodeType.ELEMENT) { - return false; - } - switch (/** @type {!Element} */ (node).tagName) { - case goog.dom.TagName.APPLET: - case goog.dom.TagName.AREA: - case goog.dom.TagName.BASE: - case goog.dom.TagName.BR: - case goog.dom.TagName.COL: - case goog.dom.TagName.COMMAND: - case goog.dom.TagName.EMBED: - case goog.dom.TagName.FRAME: - case goog.dom.TagName.HR: - case goog.dom.TagName.IMG: - case goog.dom.TagName.INPUT: - case goog.dom.TagName.IFRAME: - case goog.dom.TagName.ISINDEX: - case goog.dom.TagName.KEYGEN: - case goog.dom.TagName.LINK: - case goog.dom.TagName.NOFRAMES: - case goog.dom.TagName.NOSCRIPT: - case goog.dom.TagName.META: - case goog.dom.TagName.OBJECT: - case goog.dom.TagName.PARAM: - case goog.dom.TagName.SCRIPT: - case goog.dom.TagName.SOURCE: - case goog.dom.TagName.STYLE: - case goog.dom.TagName.TRACK: - case goog.dom.TagName.WBR: - return false; - } - return true; +goog.functions.and = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (!functions[i].apply(this, arguments)) { + return false; + } + } + return true; + }; }; /** - * Appends a child to a node. - * @param {Node} parent Parent. - * @param {Node} child Child. + * Creates a function that returns true if any of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns true. + * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ORs its component + * functions. */ -goog.dom.appendChild = function(parent, child) { - parent.appendChild(child); +goog.functions.or = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (functions[i].apply(this, arguments)) { + return true; + } + } + return false; + }; }; /** - * Appends a node with text or other nodes. - * @param {!Node} parent The node to append nodes to. - * @param {...goog.dom.Appendable} var_args The things to append to the node. - * If this is a Node it is appended as is. - * If this is a string then a text node is appended. - * If this is an array like object then fields 0 to length - 1 are appended. + * Creates a function that returns the Boolean opposite of a provided function. + * For example, (goog.functions.not(f))(x) is equivalent to !f(x). + * @param {!Function} f The original function. + * @return {function(...?):boolean} A function that delegates to f and returns + * opposite. */ -goog.dom.append = function(parent, var_args) { - goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1); +goog.functions.not = function(f) { + return function() { return !f.apply(this, arguments); }; }; /** - * Removes all the child nodes on a DOM node. - * @param {Node} node Node to remove children from. + * Generic factory function to construct an object given the constructor + * and the arguments. Intended to be bound to create object factories. + * + * Example: + * + * var factory = goog.partial(goog.functions.create, Class); + * + * @param {function(new:T, ...)} constructor The constructor for the Object. + * @param {...*} var_args The arguments to be passed to the constructor. + * @return {T} A new instance of the class given in {@code constructor}. + * @template T */ -goog.dom.removeChildren = function(node) { - // Note: Iterations over live collections can be slow, this is the fastest - // we could find. The double parenthesis are used to prevent JsCompiler and - // strict warnings. - var child; - while ((child = node.firstChild)) { - node.removeChild(child); - } -}; +goog.functions.create = function(constructor, var_args) { + /** + * @constructor + * @final + */ + var temp = function() {}; + temp.prototype = constructor.prototype; + // obj will have constructor's prototype in its chain and + // 'obj instanceof constructor' will be true. + var obj = new temp(); -/** - * Inserts a new node before an existing reference node (i.e. as the previous - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert before. - */ -goog.dom.insertSiblingBefore = function(newNode, refNode) { - if (refNode.parentNode) { - refNode.parentNode.insertBefore(newNode, refNode); - } + // obj is initialized by constructor. + // arguments is only array-like so lacks shift(), but can be used with + // the Array prototype function. + constructor.apply(obj, Array.prototype.slice.call(arguments, 1)); + return obj; }; /** - * Inserts a new node after an existing reference node (i.e. as the next - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert after. + * @define {boolean} Whether the return value cache should be used. + * This should only be used to disable caches when testing. */ -goog.dom.insertSiblingAfter = function(newNode, refNode) { - if (refNode.parentNode) { - refNode.parentNode.insertBefore(newNode, refNode.nextSibling); - } -}; +goog.define('goog.functions.CACHE_RETURN_VALUE', true); /** - * Insert a child at a given index. If index is larger than the number of child - * nodes that the parent currently has, the node is inserted as the last child - * node. - * @param {Element} parent The element into which to insert the child. - * @param {Node} child The element to insert. - * @param {number} index The index at which to insert the new child node. Must - * not be negative. + * Gives a wrapper function that caches the return value of a parameterless + * function when first called. + * + * When called for the first time, the given function is called and its + * return value is cached (thus this is only appropriate for idempotent + * functions). Subsequent calls will return the cached return value. This + * allows the evaluation of expensive functions to be delayed until first used. + * + * To cache the return values of functions with parameters, see goog.memoize. + * + * @param {!function():T} fn A function to lazily evaluate. + * @return {!function():T} A wrapped version the function. + * @template T */ -goog.dom.insertChildAt = function(parent, child, index) { - // Note that if the second argument is null, insertBefore - // will append the child at the end of the list of children. - parent.insertBefore(child, parent.childNodes[index] || null); -}; - +goog.functions.cacheReturnValue = function(fn) { + var called = false; + var value; -/** - * Removes a node from its parent. - * @param {Node} node The node to remove. - * @return {Node} The node removed if removed; else, null. - */ -goog.dom.removeNode = function(node) { - return node && node.parentNode ? node.parentNode.removeChild(node) : null; -}; + return function() { + if (!goog.functions.CACHE_RETURN_VALUE) { + return fn(); + } + if (!called) { + value = fn(); + called = true; + } -/** - * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no - * parent. - * @param {Node} newNode Node to insert. - * @param {Node} oldNode Node to replace. - */ -goog.dom.replaceNode = function(newNode, oldNode) { - var parent = oldNode.parentNode; - if (parent) { - parent.replaceChild(newNode, oldNode); - } + return value; + }; }; /** - * Flattens an element. That is, removes it and replace it with its children. - * Does nothing if the element is not in the document. - * @param {Element} element The element to flatten. - * @return {Element|undefined} The original element, detached from the document - * tree, sans children; or undefined, if the element was not in the document - * to begin with. + * Wraps a function to allow it to be called, at most, once. All + * additional calls are no-ops. + * + * This is particularly useful for initialization functions + * that should be called, at most, once. + * + * @param {function():*} f Function to call. + * @return {function():undefined} Wrapped function. */ -goog.dom.flattenElement = function(element) { - var child, parent = element.parentNode; - if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { - // Use IE DOM method (supported by Opera too) if available - if (element.removeNode) { - return /** @type {Element} */ (element.removeNode(false)); - } else { - // Move all children of the original node up one level. - while ((child = element.firstChild)) { - parent.insertBefore(child, element); - } - - // Detach the original element. - return /** @type {Element} */ (goog.dom.removeNode(element)); +goog.functions.once = function(f) { + // Keep a reference to the function that we null out when we're done with + // it -- that way, the function can be GC'd when we're done with it. + var inner = f; + return function() { + if (inner) { + var tmp = inner; + inner = null; + tmp(); } - } + }; }; /** - * Returns an array containing just the element children of the given element. - * @param {Element} element The element whose element children we want. - * @return {!(Array|NodeList)} An array or array-like list - * of just the element children of the given element. + * Wraps a function to allow it to be called, at most, once for each sequence of + * calls fired repeatedly so long as they are fired less than a specified + * interval apart (in milliseconds). Whether it receives one signal or multiple, + * it will always wait until a full interval has elapsed since the last signal + * before performing the action, passing the arguments from the last call of the + * debouncing decorator into the decorated function. + * + * This is particularly useful for bulking up repeated user actions (e.g. only + * refreshing a view once a user finishes typing rather than updating with every + * keystroke). For more stateful debouncing with support for pausing, resuming, + * and canceling debounced actions, use {@code goog.async.Debouncer}. + * + * @param {function(this:SCOPE, ...?)} f Function to call. + * @param {number} interval Interval over which to debounce. The function will + * only be called after the full interval has elapsed since the last call. + * @param {SCOPE=} opt_scope Object in whose scope to call the function. + * @return {function(...?): undefined} Wrapped function. + * @template SCOPE */ -goog.dom.getChildren = function(element) { - // We check if the children attribute is supported for child elements - // since IE8 misuses the attribute by also including comments. - if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE && - element.children != undefined) { - return element.children; +goog.functions.debounce = function(f, interval, opt_scope) { + if (opt_scope) { + f = goog.bind(f, opt_scope); } - // Fall back to manually filtering the element's child nodes. - return goog.array.filter(element.childNodes, function(node) { - return node.nodeType == goog.dom.NodeType.ELEMENT; + var timeout = null; + return /** @type {function(...?)} */ (function(var_args) { + goog.global.clearTimeout(timeout); + var args = arguments; + timeout = + goog.global.setTimeout(function() { f.apply(null, args); }, interval); }); }; /** - * Returns the first child node that is an element. - * @param {Node} node The node to get the first child element of. - * @return {Element} The first child node of {@code node} that is an element. + * Wraps a function to allow it to be called, at most, once per interval + * (specified in milliseconds). If it is called multiple times while it is + * waiting, it will only perform the action once at the end of the interval, + * passing the arguments from the last call of the throttling decorator into the + * decorated function. + * + * This is particularly useful for limiting repeated user requests (e.g. + * preventing a user from spamming a server with frequent view refreshes). For + * more stateful throttling with support for pausing, resuming, and canceling + * throttled actions, use {@code goog.async.Throttle}. + * + * @param {function(this:SCOPE, ...?)} f Function to call. + * @param {number} interval Interval over which to throttle. The function can + * only be called once per interval. + * @param {SCOPE=} opt_scope Object in whose scope to call the function. + * @return {function(...?): undefined} Wrapped function. + * @template SCOPE */ -goog.dom.getFirstElementChild = function(node) { - if (goog.isDef(node.firstElementChild)) { - return /** @type {!Element} */ (node).firstElementChild; +goog.functions.throttle = function(f, interval, opt_scope) { + if (opt_scope) { + f = goog.bind(f, opt_scope); } - return goog.dom.getNextElementNode_(node.firstChild, true); -}; + var timeout = null; + var shouldFire = false; + var args = []; + var handleTimeout = function() { + timeout = null; + if (shouldFire) { + shouldFire = false; + fire(); + } + }; -/** - * Returns the last child node that is an element. - * @param {Node} node The node to get the last child element of. - * @return {Element} The last child node of {@code node} that is an element. - */ -goog.dom.getLastElementChild = function(node) { - if (goog.isDef(node.lastElementChild)) { - return /** @type {!Element} */ (node).lastElementChild; - } - return goog.dom.getNextElementNode_(node.lastChild, false); + var fire = function() { + timeout = goog.global.setTimeout(handleTimeout, interval); + f.apply(null, args); + }; + + return /** @type {function(...?)} */ (function(var_args) { + args = arguments; + if (!timeout) { + fire(); + } else { + shouldFire = true; + } + }); }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns the first next sibling that is an element. - * @param {Node} node The node to get the next sibling element of. - * @return {Element} The next sibling of {@code node} that is an element. + * @fileoverview Provides a function to schedule running a function as soon + * as possible after the current JS execution stops and yields to the event + * loop. + * */ -goog.dom.getNextElementSibling = function(node) { - if (goog.isDef(node.nextElementSibling)) { - return /** @type {!Element} */ (node).nextElementSibling; - } - return goog.dom.getNextElementNode_(node.nextSibling, true); -}; +goog.provide('goog.async.nextTick'); +goog.provide('goog.async.throwException'); -/** - * Returns the first previous sibling that is an element. - * @param {Node} node The node to get the previous sibling element of. - * @return {Element} The first previous sibling of {@code node} that is - * an element. - */ -goog.dom.getPreviousElementSibling = function(node) { - if (goog.isDef(node.previousElementSibling)) { - return /** @type {!Element} */ (node).previousElementSibling; - } - return goog.dom.getNextElementNode_(node.previousSibling, false); -}; +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.dom.TagName'); +goog.require('goog.functions'); +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.labs.userAgent.engine'); /** - * Returns the first node that is an element in the specified direction, - * starting with {@code node}. - * @param {Node} node The node to get the next element from. - * @param {boolean} forward Whether to look forwards or backwards. - * @return {Element} The first element. - * @private + * Throw an item without interrupting the current execution context. For + * example, if processing a group of items in a loop, sometimes it is useful + * to report an error while still allowing the rest of the batch to be + * processed. + * @param {*} exception */ -goog.dom.getNextElementNode_ = function(node, forward) { - while (node && node.nodeType != goog.dom.NodeType.ELEMENT) { - node = forward ? node.nextSibling : node.previousSibling; - } - - return /** @type {Element} */ (node); +goog.async.throwException = function(exception) { + // Each throw needs to be in its own context. + goog.global.setTimeout(function() { throw exception; }, 0); }; /** - * Returns the next node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The next node in the DOM tree, or null if this was the last - * node. + * Fires the provided callbacks as soon as possible after the current JS + * execution context. setTimeout(…, 0) takes at least 4ms when called from + * within another setTimeout(…, 0) for legacy reasons. + * + * This will not schedule the callback as a microtask (i.e. a task that can + * preempt user input or networking callbacks). It is meant to emulate what + * setTimeout(_, 0) would do if it were not throttled. If you desire microtask + * behavior, use {@see goog.Promise} instead. + * + * @param {function(this:SCOPE)} callback Callback function to fire as soon as + * possible. + * @param {SCOPE=} opt_context Object in whose scope to call the listener. + * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that + * ensures correctness at the cost of speed. See comments for details. + * @template SCOPE */ -goog.dom.getNextNode = function(node) { - if (!node) { - return null; +goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) { + var cb = callback; + if (opt_context) { + cb = goog.bind(callback, opt_context); } - - if (node.firstChild) { - return node.firstChild; + cb = goog.async.nextTick.wrapCallback_(cb); + // Note we do allow callers to also request setImmediate if they are willing + // to accept the possible tradeoffs of incorrectness in exchange for speed. + // The IE fallback of readystate change is much slower. See useSetImmediate_ + // for details. + if (goog.isFunction(goog.global.setImmediate) && + (opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) { + goog.global.setImmediate(cb); + return; } - while (node && !node.nextSibling) { - node = node.parentNode; + // Look for and cache the custom fallback version of setImmediate. + if (!goog.async.nextTick.setImmediate_) { + goog.async.nextTick.setImmediate_ = + goog.async.nextTick.getSetImmediateEmulator_(); } - - return node ? node.nextSibling : null; + goog.async.nextTick.setImmediate_(cb); }; /** - * Returns the previous node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The previous node in the DOM tree, or null if this was the - * first node. + * Returns whether should use setImmediate implementation currently on window. + * + * window.setImmediate was introduced and currently only supported by IE10+, + * but due to a bug in the implementation it is not guaranteed that + * setImmediate is faster than setTimeout nor that setImmediate N is before + * setImmediate N+1. That is why we do not use the native version if + * available. We do, however, call setImmediate if it is a non-native function + * because that indicates that it has been replaced by goog.testing.MockClock + * which we do want to support. + * See + * http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10 + * + * @return {boolean} Whether to use the implementation of setImmediate defined + * on Window. + * @private */ -goog.dom.getPreviousNode = function(node) { - if (!node) { - return null; - } - - if (!node.previousSibling) { - return node.parentNode; +goog.async.nextTick.useSetImmediate_ = function() { + // Not a browser environment. + if (!goog.global.Window || !goog.global.Window.prototype) { + return true; } - node = node.previousSibling; - while (node && node.lastChild) { - node = node.lastChild; + // MS Edge has window.setImmediate natively, but it's not on Window.prototype. + // Also, there's no clean way to detect if the goog.global.setImmediate has + // been replaced by mockClock as its replacement also shows up as "[native + // code]" when using toString. Therefore, just always use + // goog.global.setImmediate for Edge. It's unclear if it suffers the same + // issues as IE10/11, but based on + // https://dev.modern.ie/testdrive/demos/setimmediatesorting/ + // it seems they've been working to ensure it's WAI. + if (goog.labs.userAgent.browser.isEdge() || + goog.global.Window.prototype.setImmediate != goog.global.setImmediate) { + // Something redefined setImmediate in which case we decide to use it (This + // is so that we use the mockClock setImmediate). + return true; } - return node; -}; - - -/** - * Whether the object looks like a DOM node. - * @param {?} obj The object being tested for node likeness. - * @return {boolean} Whether the object looks like a DOM node. - */ -goog.dom.isNodeLike = function(obj) { - return goog.isObject(obj) && obj.nodeType > 0; -}; - - -/** - * Whether the object looks like an Element. - * @param {?} obj The object being tested for Element likeness. - * @return {boolean} Whether the object looks like an Element. - */ -goog.dom.isElement = function(obj) { - return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT; -}; - - -/** - * Returns true if the specified value is a Window object. This includes the - * global window for HTML pages, and iframe windows. - * @param {?} obj Variable to test. - * @return {boolean} Whether the variable is a window. - */ -goog.dom.isWindow = function(obj) { - return goog.isObject(obj) && obj['window'] == obj; + return false; }; /** - * Returns an element's parent, if it's an Element. - * @param {Element} element The DOM element. - * @return {Element} The parent, or null if not an Element. + * Cache for the setImmediate implementation. + * @type {function(function())} + * @private */ -goog.dom.getParentElement = function(element) { - var parent; - if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) { - var isIe9 = goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9') && - !goog.userAgent.isVersionOrHigher('10'); - // SVG elements in IE9 can't use the parentElement property. - // goog.global['SVGElement'] is not defined in IE9 quirks mode. - if (!(isIe9 && goog.global['SVGElement'] && - element instanceof goog.global['SVGElement'])) { - parent = element.parentElement; - if (parent) { - return parent; - } - } - } - parent = element.parentNode; - return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null; -}; +goog.async.nextTick.setImmediate_; /** - * Whether a node contains another node. - * @param {?Node} parent The node that should contain the other node. - * @param {?Node} descendant The node to test presence of. - * @return {boolean} Whether the parent node contains the descendent node. + * Determines the best possible implementation to run a function as soon as + * the JS event loop is idle. + * @return {function(function())} The "setImmediate" implementation. + * @private */ -goog.dom.contains = function(parent, descendant) { - if (!parent || !descendant) { - return false; - } - // We use browser specific methods for this if available since it is faster - // that way. - - // IE DOM - if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { - return parent == descendant || parent.contains(descendant); - } - - // W3C DOM Level 3 - if (typeof parent.compareDocumentPosition != 'undefined') { - return parent == descendant || - Boolean(parent.compareDocumentPosition(descendant) & 16); - } - - // W3C DOM Level 1 - while (descendant && parent != descendant) { - descendant = descendant.parentNode; +goog.async.nextTick.getSetImmediateEmulator_ = function() { + // Create a private message channel and use it to postMessage empty messages + // to ourselves. + var Channel = goog.global['MessageChannel']; + // If MessageChannel is not available and we are in a browser, implement + // an iframe based polyfill in browsers that have postMessage and + // document.addEventListener. The latter excludes IE8 because it has a + // synchronous postMessage implementation. + if (typeof Channel === 'undefined' && typeof window !== 'undefined' && + window.postMessage && window.addEventListener && + // Presto (The old pre-blink Opera engine) has problems with iframes + // and contentWindow. + !goog.labs.userAgent.engine.isPresto()) { + /** @constructor */ + Channel = function() { + // Make an empty, invisible iframe. + var iframe = /** @type {!HTMLIFrameElement} */ ( + document.createElement(goog.dom.TagName.IFRAME)); + iframe.style.display = 'none'; + iframe.src = ''; + document.documentElement.appendChild(iframe); + var win = iframe.contentWindow; + var doc = win.document; + doc.open(); + doc.write(''); + doc.close(); + // Do not post anything sensitive over this channel, as the workaround for + // pages with file: origin could allow that information to be modified or + // intercepted. + var message = 'callImmediate' + Math.random(); + // The same origin policy rejects attempts to postMessage from file: urls + // unless the origin is '*'. + // TODO(b/16335441): Use '*' origin for data: and other similar protocols. + var origin = win.location.protocol == 'file:' ? + '*' : + win.location.protocol + '//' + win.location.host; + var onmessage = goog.bind(function(e) { + // Validate origin and message to make sure that this message was + // intended for us. If the origin is set to '*' (see above) only the + // message needs to match since, for example, '*' != 'file://'. Allowing + // the wildcard is ok, as we are not concerned with security here. + if ((origin != '*' && e.origin != origin) || e.data != message) { + return; + } + this['port1'].onmessage(); + }, this); + win.addEventListener('message', onmessage, false); + this['port1'] = {}; + this['port2'] = { + postMessage: function() { win.postMessage(message, origin); } + }; + }; + } + if (typeof Channel !== 'undefined' && (!goog.labs.userAgent.browser.isIE())) { + // Exclude all of IE due to + // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ + // which allows starving postMessage with a busy setTimeout loop. + // This currently affects IE10 and IE11 which would otherwise be able + // to use the postMessage based fallbacks. + var channel = new Channel(); + // Use a fifo linked list to call callbacks in the right order. + var head = {}; + var tail = head; + channel['port1'].onmessage = function() { + if (goog.isDef(head.next)) { + head = head.next; + var cb = head.cb; + head.cb = null; + cb(); + } + }; + return function(cb) { + tail.next = {cb: cb}; + tail = tail.next; + channel['port2'].postMessage(0); + }; + } + // Implementation for IE6 to IE10: Script elements fire an asynchronous + // onreadystatechange event when inserted into the DOM. + if (typeof document !== 'undefined' && + 'onreadystatechange' in document.createElement(goog.dom.TagName.SCRIPT)) { + return function(cb) { + var script = document.createElement(goog.dom.TagName.SCRIPT); + script.onreadystatechange = function() { + // Clean up and call the callback. + script.onreadystatechange = null; + script.parentNode.removeChild(script); + script = null; + cb(); + cb = null; + }; + document.documentElement.appendChild(script); + }; } - return descendant == parent; + // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms + // or more. + // NOTE(user): This fallback is used for IE11. + return function(cb) { goog.global.setTimeout(cb, 0); }; }; /** - * Compares the document order of two nodes, returning 0 if they are the same - * node, a negative number if node1 is before node2, and a positive number if - * node2 is before node1. Note that we compare the order the tags appear in the - * document so in the tree text the B node is considered to be - * before the I node. + * Helper function that is overrided to protect callbacks with entry point + * monitor if the application monitors entry points. + * @param {function()} callback Callback function to fire as soon as possible. + * @return {function()} The wrapped callback. + * @private + */ +goog.async.nextTick.wrapCallback_ = goog.functions.identity; + + +// Register the callback function as an entry point, so that it can be +// monitored for exception handling, etc. This has to be done in this file +// since it requires special code to handle all browsers. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { goog.async.nextTick.wrapCallback_ = transformer; }); + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.PointerEvent'); + + +goog.require('ol.events'); +goog.require('ol.events.Event'); + + +/** + * A class for pointer events. * - * @param {Node} node1 The first node to compare. - * @param {Node} node2 The second node to compare. - * @return {number} 0 if the nodes are the same node, a negative number if node1 - * is before node2, and a positive number if node2 is before node1. + * This class is used as an abstraction for mouse events, + * touch events and even native pointer events. + * + * @constructor + * @extends {ol.events.Event} + * @param {string} type The type of the event to create. + * @param {Event} originalEvent The event. + * @param {Object.=} opt_eventDict An optional dictionary of + * initial event properties. */ -goog.dom.compareNodeOrder = function(node1, node2) { - // Fall out quickly for equality. - if (node1 == node2) { - return 0; - } +ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) { + ol.events.Event.call(this, type); - // Use compareDocumentPosition where available - if (node1.compareDocumentPosition) { - // 4 is the bitmask for FOLLOWS. - return node1.compareDocumentPosition(node2) & 2 ? 1 : -1; - } + /** + * @const + * @type {Event} + */ + this.originalEvent = originalEvent; - // Special case for document nodes on IE 7 and 8. - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { - if (node1.nodeType == goog.dom.NodeType.DOCUMENT) { - return -1; - } - if (node2.nodeType == goog.dom.NodeType.DOCUMENT) { - return 1; - } - } + var eventDict = opt_eventDict ? opt_eventDict : {}; - // Process in IE using sourceIndex - we check to see if the first node has - // a source index or if its parent has one. - if ('sourceIndex' in node1 || - (node1.parentNode && 'sourceIndex' in node1.parentNode)) { - var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT; - var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; + /** + * @type {number} + */ + this.buttons = this.getButtons_(eventDict); - if (isElement1 && isElement2) { - return node1.sourceIndex - node2.sourceIndex; - } else { - var parent1 = node1.parentNode; - var parent2 = node2.parentNode; + /** + * @type {number} + */ + this.pressure = this.getPressure_(eventDict, this.buttons); - if (parent1 == parent2) { - return goog.dom.compareSiblingOrder_(node1, node2); - } + // MouseEvent related properties - if (!isElement1 && goog.dom.contains(parent1, node2)) { - return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2); - } + /** + * @type {boolean} + */ + this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false; + /** + * @type {boolean} + */ + this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false; - if (!isElement2 && goog.dom.contains(parent2, node1)) { - return goog.dom.compareParentsDescendantNodeIe_(node2, node1); - } + /** + * @type {Object} + */ + this.view = 'view' in eventDict ? eventDict['view'] : null; - return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) - - (isElement2 ? node2.sourceIndex : parent2.sourceIndex); - } - } + /** + * @type {number} + */ + this.detail = 'detail' in eventDict ? eventDict['detail'] : null; - // For Safari, we compare ranges. - var doc = goog.dom.getOwnerDocument(node1); + /** + * @type {number} + */ + this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0; - var range1, range2; - range1 = doc.createRange(); - range1.selectNode(node1); - range1.collapse(true); + /** + * @type {number} + */ + this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0; - range2 = doc.createRange(); - range2.selectNode(node2); - range2.collapse(true); + /** + * @type {number} + */ + this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0; - return range1.compareBoundaryPoints( - goog.global['Range'].START_TO_END, range2); -}; + /** + * @type {number} + */ + this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0; + /** + * @type {boolean} + */ + this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false; -/** - * Utility function to compare the position of two nodes, when - * {@code textNode}'s parent is an ancestor of {@code node}. If this entry - * condition is not met, this function will attempt to reference a null object. - * @param {!Node} textNode The textNode to compare. - * @param {Node} node The node to compare. - * @return {number} -1 if node is before textNode, +1 otherwise. - * @private - */ -goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { - var parent = textNode.parentNode; - if (parent == node) { - // If textNode is a child of node, then node comes first. - return -1; - } - var sibling = node; - while (sibling.parentNode != parent) { - sibling = sibling.parentNode; - } - return goog.dom.compareSiblingOrder_(sibling, textNode); -}; + /** + * @type {boolean} + */ + this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false; + /** + * @type {boolean} + */ + this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false; -/** - * Utility function to compare the position of two nodes known to be non-equal - * siblings. - * @param {Node} node1 The first node to compare. - * @param {!Node} node2 The second node to compare. - * @return {number} -1 if node1 is before node2, +1 otherwise. - * @private - */ -goog.dom.compareSiblingOrder_ = function(node1, node2) { - var s = node2; - while ((s = s.previousSibling)) { - if (s == node1) { - // We just found node1 before node2. - return -1; - } - } + /** + * @type {boolean} + */ + this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false; - // Since we didn't find it, node1 must be after node2. - return 1; -}; + /** + * @type {number} + */ + this.button = 'button' in eventDict ? eventDict['button'] : 0; + /** + * @type {Node} + */ + this.relatedTarget = 'relatedTarget' in eventDict ? + eventDict['relatedTarget'] : null; -/** - * Find the deepest common ancestor of the given nodes. - * @param {...Node} var_args The nodes to find a common ancestor of. - * @return {Node} The common ancestor of the nodes, or null if there is none. - * null will only be returned if two or more of the nodes are from different - * documents. - */ -goog.dom.findCommonAncestor = function(var_args) { - var i, count = arguments.length; - if (!count) { - return null; - } else if (count == 1) { - return arguments[0]; - } - - var paths = []; - var minLength = Infinity; - for (i = 0; i < count; i++) { - // Compute the list of ancestors. - var ancestors = []; - var node = arguments[i]; - while (node) { - ancestors.unshift(node); - node = node.parentNode; - } - - // Save the list for comparison. - paths.push(ancestors); - minLength = Math.min(minLength, ancestors.length); - } - var output = null; - for (i = 0; i < minLength; i++) { - var first = paths[0][i]; - for (var j = 1; j < count; j++) { - if (first != paths[j][i]) { - return output; - } - } - output = first; - } - return output; -}; + // PointerEvent related properties + /** + * @const + * @type {number} + */ + this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0; -/** - * Returns the owner document for a node. - * @param {Node|Window} node The node to get the document for. - * @return {!Document} The document owning the node. - */ -goog.dom.getOwnerDocument = function(node) { - // TODO(nnaze): Update param signature to be non-nullable. - goog.asserts.assert(node, 'Node cannot be null or undefined.'); - return /** @type {!Document} */ ( - node.nodeType == goog.dom.NodeType.DOCUMENT ? node : node.ownerDocument || - node.document); -}; + /** + * @type {number} + */ + this.width = 'width' in eventDict ? eventDict['width'] : 0; + /** + * @type {number} + */ + this.height = 'height' in eventDict ? eventDict['height'] : 0; -/** - * Cross-browser function for getting the document element of a frame or iframe. - * @param {Element} frame Frame element. - * @return {!Document} The frame content document. - */ -goog.dom.getFrameContentDocument = function(frame) { - return frame.contentDocument || - /** @type {!HTMLFrameElement} */ (frame).contentWindow.document; -}; + /** + * @type {number} + */ + this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0; + /** + * @type {number} + */ + this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0; -/** - * Cross-browser function for getting the window of a frame or iframe. - * @param {Element} frame Frame element. - * @return {Window} The window associated with the given frame, or null if none - * exists. - */ -goog.dom.getFrameContentWindow = function(frame) { - try { - return frame.contentWindow || - (frame.contentDocument ? goog.dom.getWindow(frame.contentDocument) : - null); - } catch (e) { - // NOTE(user): In IE8, checking the contentWindow or contentDocument - // properties will throw a "Unspecified Error" exception if the iframe is - // not inserted in the DOM. If we get this we can be sure that no window - // exists, so return null. - } - return null; -}; + /** + * @type {string} + */ + this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : ''; + /** + * @type {number} + */ + this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0; -/** - * Sets the text content of a node, with cross-browser support. - * @param {Node} node The node to change the text content of. - * @param {string|number} text The value that should replace the node's content. - */ -goog.dom.setTextContent = function(node, text) { - goog.asserts.assert( - node != null, - 'goog.dom.setTextContent expects a non-null value for node'); + /** + * @type {boolean} + */ + this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false; - if ('textContent' in node) { - node.textContent = text; - } else if (node.nodeType == goog.dom.NodeType.TEXT) { - node.data = text; - } else if ( - node.firstChild && node.firstChild.nodeType == goog.dom.NodeType.TEXT) { - // If the first child is a text node we just change its data and remove the - // rest of the children. - while (node.lastChild != node.firstChild) { - node.removeChild(node.lastChild); - } - node.firstChild.data = text; - } else { - goog.dom.removeChildren(node); - var doc = goog.dom.getOwnerDocument(node); - node.appendChild(doc.createTextNode(String(text))); + // keep the semantics of preventDefault + if (originalEvent.preventDefault) { + this.preventDefault = function() { + originalEvent.preventDefault(); + }; } }; +ol.inherits(ol.pointer.PointerEvent, ol.events.Event); /** - * Gets the outerHTML of a node, which islike innerHTML, except that it - * actually contains the HTML of the node itself. - * @param {Element} element The element to get the HTML of. - * @return {string} The outerHTML of the given element. + * @private + * @param {Object.} eventDict The event dictionary. + * @return {number} Button indicator. */ -goog.dom.getOuterHtml = function(element) { - goog.asserts.assert( - element !== null, - 'goog.dom.getOuterHtml expects a non-null value for element'); - // IE, Opera and WebKit all have outerHTML. - if ('outerHTML' in element) { - return element.outerHTML; +ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) { + // According to the w3c spec, + // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button + // MouseEvent.button == 0 can mean either no mouse button depressed, or the + // left mouse button depressed. + // + // As of now, the only way to distinguish between the two states of + // MouseEvent.button is by using the deprecated MouseEvent.which property, as + // this maps mouse buttons to positive integers > 0, and uses 0 to mean that + // no mouse button is held. + // + // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation, + // but initMouseEvent does not expose an argument with which to set + // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set + // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations + // of app developers. + // + // The only way to propagate the correct state of MouseEvent.which and + // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0 + // is to call initMouseEvent with a buttonArg value of -1. + // + // This is fixed with DOM Level 4's use of buttons + var buttons; + if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) { + buttons = eventDict.buttons; } else { - var doc = goog.dom.getOwnerDocument(element); - var div = doc.createElement(goog.dom.TagName.DIV); - div.appendChild(element.cloneNode(true)); - return div.innerHTML; + switch (eventDict.which) { + case 1: buttons = 1; break; + case 2: buttons = 4; break; + case 3: buttons = 2; break; + default: buttons = 0; + } } + return buttons; }; /** - * Finds the first descendant node that matches the filter function, using - * a depth first search. This function offers the most general purpose way - * of finding a matching element. You may also wish to consider - * {@code goog.dom.query} which can express many matching criteria using - * CSS selector expressions. These expressions often result in a more - * compact representation of the desired result. - * @see goog.dom.query - * - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Node|undefined} The found node or undefined if none is found. + * @private + * @param {Object.} eventDict The event dictionary. + * @param {number} buttons Button indicator. + * @return {number} The pressure. */ -goog.dom.findNode = function(root, p) { - var rv = []; - var found = goog.dom.findNodes_(root, p, rv, true); - return found ? rv[0] : undefined; +ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) { + // Spec requires that pointers without pressure specified use 0.5 for down + // state and 0 for up state. + var pressure = 0; + if (eventDict.pressure) { + pressure = eventDict.pressure; + } else { + pressure = buttons ? 0.5 : 0; + } + return pressure; }; /** - * Finds all the descendant nodes that match the filter function, using a - * a depth first search. This function offers the most general-purpose way - * of finding a set of matching elements. You may also wish to consider - * {@code goog.dom.query} which can express many matching criteria using - * CSS selector expressions. These expressions often result in a more - * compact representation of the desired result. - - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {!Array} The found nodes or an empty array if none are found. + * Is the `buttons` property supported? + * @type {boolean} */ -goog.dom.findNodes = function(root, p) { - var rv = []; - goog.dom.findNodes_(root, p, rv, false); - return rv; -}; +ol.pointer.PointerEvent.HAS_BUTTONS = false; /** - * Finds the first or all the descendant nodes that match the filter function, - * using a depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @param {!Array} rv The found nodes are added to this array. - * @param {boolean} findOne If true we exit after the first found node. - * @return {boolean} Whether the search is complete or not. True in case findOne - * is true and the node is found. False otherwise. - * @private + * Checks if the `buttons` property is supported. */ -goog.dom.findNodes_ = function(root, p, rv, findOne) { - if (root != null) { - var child = root.firstChild; - while (child) { - if (p(child)) { - rv.push(child); - if (findOne) { - return true; - } - } - if (goog.dom.findNodes_(child, p, rv, findOne)) { - return true; - } - child = child.nextSibling; - } +(function() { + try { + var ev = new MouseEvent('click', {buttons: 1}); + ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1; + } catch (e) { + // pass } - return false; -}; - +})(); -/** - * Map of tags whose content to ignore when calculating text length. - * @private {!Object} - * @const - */ -goog.dom.TAGS_TO_IGNORE_ = { - 'SCRIPT': 1, - 'STYLE': 1, - 'HEAD': 1, - 'IFRAME': 1, - 'OBJECT': 1 -}; +goog.provide('ol.webgl'); +goog.provide('ol.webgl.WebGLContextEventType'); /** - * Map of tags which have predefined values with regard to whitespace. - * @private {!Object} * @const + * @private + * @type {Array.} */ -goog.dom.PREDEFINED_TAG_VALUES_ = { - 'IMG': ' ', - 'BR': '\n' -}; +ol.webgl.CONTEXT_IDS_ = [ + 'experimental-webgl', + 'webgl', + 'webkit-3d', + 'moz-webgl' +]; /** - * Returns true if the element has a tab index that allows it to receive - * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements - * natively support keyboard focus, even if they have no tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a tab index that allows keyboard - * focus. + * @enum {string} */ -goog.dom.isFocusableTabIndex = function(element) { - return goog.dom.hasSpecifiedTabIndex_(element) && - goog.dom.isTabIndexFocusable_(element); +ol.webgl.WebGLContextEventType = { + LOST: 'webglcontextlost', + RESTORED: 'webglcontextrestored' }; /** - * Enables or disables keyboard focus support on the element via its tab index. - * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true - * (or elements that natively support keyboard focus, like form elements) can - * receive keyboard focus. See http://go/tabindex for more info. - * @param {Element} element Element whose tab index is to be changed. - * @param {boolean} enable Whether to set or remove a tab index on the element - * that supports keyboard focus. + * @param {HTMLCanvasElement} canvas Canvas. + * @param {Object=} opt_attributes Attributes. + * @return {WebGLRenderingContext} WebGL rendering context. */ -goog.dom.setFocusableTabIndex = function(element, enable) { - if (enable) { - element.tabIndex = 0; - } else { - // Set tabIndex to -1 first, then remove it. This is a workaround for - // Safari (confirmed in version 4 on Windows). When removing the attribute - // without setting it to -1 first, the element remains keyboard focusable - // despite not having a tabIndex attribute anymore. - element.tabIndex = -1; - element.removeAttribute('tabIndex'); // Must be camelCase! +ol.webgl.getContext = function(canvas, opt_attributes) { + var context, i, ii = ol.webgl.CONTEXT_IDS_.length; + for (i = 0; i < ii; ++i) { + try { + context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); + if (context) { + return /** @type {!WebGLRenderingContext} */ (context); + } + } catch (e) { + // pass + } } + return null; }; +goog.provide('ol.has'); -/** - * Returns true if the element can be focused, i.e. it has a tab index that - * allows it to receive keyboard focus (tabIndex >= 0), or it is an element - * that natively supports keyboard focus. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element allows keyboard focus. - */ -goog.dom.isFocusable = function(element) { - var focusable; - // Some elements can have unspecified tab index and still receive focus. - if (goog.dom.nativelySupportsFocus_(element)) { - // Make sure the element is not disabled ... - focusable = !element.disabled && - // ... and if a tab index is specified, it allows focus. - (!goog.dom.hasSpecifiedTabIndex_(element) || - goog.dom.isTabIndexFocusable_(element)); - } else { - focusable = goog.dom.isFocusableTabIndex(element); - } - - // IE requires elements to be visible in order to focus them. - return focusable && goog.userAgent.IE ? - goog.dom.hasNonZeroBoundingRect_(/** @type {!HTMLElement} */ (element)) : - focusable; -}; - +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.webgl'); -/** - * Returns true if the element has a specified tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a specified tab index. - * @private - */ -goog.dom.hasSpecifiedTabIndex_ = function(element) { - // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(), - // which returns an object with a 'specified' property if tabIndex is - // specified. This works on other browsers, too. - var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase! - return goog.isDefAndNotNull(attrNode) && attrNode.specified; -}; +var ua = typeof navigator !== 'undefined' ? + navigator.userAgent.toLowerCase() : ''; /** - * Returns true if the element's tab index allows the element to be focused. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element's tab index allows focus. - * @private + * User agent string says we are dealing with Firefox as browser. + * @type {boolean} */ -goog.dom.isTabIndexFocusable_ = function(element) { - var index = /** @type {!HTMLElement} */ (element).tabIndex; - // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534. - return goog.isNumber(index) && index >= 0 && index < 32768; -}; - +ol.has.FIREFOX = ua.indexOf('firefox') !== -1; /** - * Returns true if the element is focusable even when tabIndex is not set. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element natively supports focus. - * @private + * User agent string says we are dealing with Safari as browser. + * @type {boolean} */ -goog.dom.nativelySupportsFocus_ = function(element) { - return element.tagName == goog.dom.TagName.A || - element.tagName == goog.dom.TagName.INPUT || - element.tagName == goog.dom.TagName.TEXTAREA || - element.tagName == goog.dom.TagName.SELECT || - element.tagName == goog.dom.TagName.BUTTON; -}; - +ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') === -1; /** - * Returns true if the element has a bounding rectangle that would be visible - * (i.e. its width and height are greater than zero). - * @param {!HTMLElement} element Element to check. - * @return {boolean} Whether the element has a non-zero bounding rectangle. - * @private + * User agent string says we are dealing with a Mac as platform. + * @type {boolean} */ -goog.dom.hasNonZeroBoundingRect_ = function(element) { - var rect; - if (!goog.isFunction(element['getBoundingClientRect']) || - // In IE, getBoundingClientRect throws on detached nodes. - (goog.userAgent.IE && element.parentElement == null)) { - rect = {'height': element.offsetHeight, 'width': element.offsetWidth}; - } else { - rect = element.getBoundingClientRect(); - } - return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0; -}; +ol.has.MAC = ua.indexOf('macintosh') !== -1; /** - * Returns the text content of the current node, without markup and invisible - * symbols. New lines are stripped and whitespace is collapsed, - * such that each character would be visible. - * - * In browsers that support it, innerText is used. Other browsers attempt to - * simulate it via node traversal. Line breaks are canonicalized in IE. - * - * @param {Node} node The node from which we are getting content. - * @return {string} The text content. + * The ratio between physical pixels and device-independent pixels + * (dips) on the device (`window.devicePixelRatio`). + * @const + * @type {number} + * @api stable */ -goog.dom.getTextContent = function(node) { - var textContent; - // Note(arv): IE9, Opera, and Safari 3 support innerText but they include - // text nodes in script tags. So we revert to use a user agent test here. - if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && node !== null && - ('innerText' in node)) { - textContent = goog.string.canonicalizeNewlines(node.innerText); - // Unfortunately .innerText() returns text with ­ symbols - // We need to filter it out and then remove duplicate whitespaces - } else { - var buf = []; - goog.dom.getTextContent_(node, buf, true); - textContent = buf.join(''); - } - - // Strip ­ entities. goog.format.insertWordBreaks inserts them in Opera. - textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, ''); - // Strip ​ entities. goog.format.insertWordBreaks inserts them in IE8. - textContent = textContent.replace(/\u200B/g, ''); - - // Skip this replacement on old browsers with working innerText, which - // automatically turns   into ' ' and / +/ into ' ' when reading - // innerText. - if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) { - textContent = textContent.replace(/ +/g, ' '); - } - if (textContent != ' ') { - textContent = textContent.replace(/^\s*/, ''); - } - - return textContent; -}; +ol.has.DEVICE_PIXEL_RATIO = ol.global.devicePixelRatio || 1; /** - * Returns the text content of the current node, without markup. - * - * Unlike {@code getTextContent} this method does not collapse whitespaces - * or normalize lines breaks. - * - * @param {Node} node The node from which we are getting content. - * @return {string} The raw text content. + * True if the browser's Canvas implementation implements {get,set}LineDash. + * @type {boolean} */ -goog.dom.getRawTextContent = function(node) { - var buf = []; - goog.dom.getTextContent_(node, buf, false); - - return buf.join(''); -}; +ol.has.CANVAS_LINE_DASH = false; /** - * Recursive support function for text content retrieval. - * - * @param {Node} node The node from which we are getting content. - * @param {Array} buf string buffer. - * @param {boolean} normalizeWhitespace Whether to normalize whitespace. - * @private + * True if both the library and browser support Canvas. Always `false` + * if `ol.ENABLE_CANVAS` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable */ -goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { - if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) { - // ignore certain tags - } else if (node.nodeType == goog.dom.NodeType.TEXT) { - if (normalizeWhitespace) { - buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); - } else { - buf.push(node.nodeValue); - } - } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { - buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]); - } else { - var child = node.firstChild; - while (child) { - goog.dom.getTextContent_(child, buf, normalizeWhitespace); - child = child.nextSibling; - } - } -}; +ol.has.CANVAS = ol.ENABLE_CANVAS && ( + /** + * @return {boolean} Canvas supported. + */ + function() { + if (!('HTMLCanvasElement' in ol.global)) { + return false; + } + try { + var context = ol.dom.createCanvasContext2D(); + if (!context) { + return false; + } else { + if (context.setLineDash !== undefined) { + ol.has.CANVAS_LINE_DASH = true; + } + return true; + } + } catch (e) { + return false; + } + })(); /** - * Returns the text length of the text contained in a node, without markup. This - * is equivalent to the selection length if the node was selected, or the number - * of cursor movements to traverse the node. Images & BRs take one space. New - * lines are ignored. - * - * @param {Node} node The node whose text content length is being calculated. - * @return {number} The length of {@code node}'s text content. + * Indicates if DeviceOrientation is supported in the user's browser. + * @const + * @type {boolean} + * @api stable */ -goog.dom.getNodeTextLength = function(node) { - return goog.dom.getTextContent(node).length; -}; +ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in ol.global; /** - * Returns the text offset of a node relative to one of its ancestors. The text - * length is the same as the length calculated by goog.dom.getNodeTextLength. - * - * @param {Node} node The node whose offset is being calculated. - * @param {Node=} opt_offsetParent The node relative to which the offset will - * be calculated. Defaults to the node's owner document's body. - * @return {number} The text offset. + * True if `ol.ENABLE_DOM` is set to `true` at compile time. + * @const + * @type {boolean} */ -goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { - var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; - var buf = []; - while (node && node != root) { - var cur = node; - while ((cur = cur.previousSibling)) { - buf.unshift(goog.dom.getTextContent(cur)); - } - node = node.parentNode; - } - // Trim left to deal with FF cases when there might be line breaks and empty - // nodes at the front of the text - return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length; -}; +ol.has.DOM = ol.ENABLE_DOM; /** - * Returns the node at a given offset in a parent node. If an object is - * provided for the optional third parameter, the node and the remainder of the - * offset will stored as properties of this object. - * @param {Node} parent The parent node. - * @param {number} offset The offset into the parent node. - * @param {Object=} opt_result Object to be used to store the return value. The - * return value will be stored in the form {node: Node, remainder: number} - * if this object is provided. - * @return {Node} The node at the given offset. + * Is HTML5 geolocation supported in the current browser? + * @const + * @type {boolean} + * @api stable */ -goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { - var stack = [parent], pos = 0, cur = null; - while (stack.length > 0 && pos < offset) { - cur = stack.pop(); - if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) { - // ignore certain tags - } else if (cur.nodeType == goog.dom.NodeType.TEXT) { - var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' '); - pos += text.length; - } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { - pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length; - } else { - for (var i = cur.childNodes.length - 1; i >= 0; i--) { - stack.push(cur.childNodes[i]); - } - } - } - if (goog.isObject(opt_result)) { - opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; - opt_result.node = cur; - } - - return cur; -}; +ol.has.GEOLOCATION = 'geolocation' in ol.global.navigator; /** - * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, - * the object must have a numeric length property and an item function (which - * has type 'string' on IE for some reason). - * @param {Object} val Object to test. - * @return {boolean} Whether the object is a NodeList. + * True if browser supports touch events. + * @const + * @type {boolean} + * @api stable */ -goog.dom.isNodeList = function(val) { - // TODO(attila): Now the isNodeList is part of goog.dom we can use - // goog.userAgent to make this simpler. - // A NodeList must have a length property of type 'number' on all platforms. - if (val && typeof val.length == 'number') { - // A NodeList is an object everywhere except Safari, where it's a function. - if (goog.isObject(val)) { - // A NodeList must have an item function (on non-IE platforms) or an item - // property of type 'string' (on IE). - return typeof val.item == 'function' || typeof val.item == 'string'; - } else if (goog.isFunction(val)) { - // On Safari, a NodeList is a function with an item property that is also - // a function. - return typeof val.item == 'function'; - } - } - - // Not a NodeList. - return false; -}; +ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in ol.global; /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * tag name and/or class name. If the passed element matches the specified - * criteria, the element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or - * null/undefined to match only based on class name). - * @param {?string=} opt_class The class name to match (or null/undefined to - * match only based on tag name). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if no match is found. + * True if browser supports pointer events. + * @const + * @type {boolean} */ -goog.dom.getAncestorByTagNameAndClass = function( - element, opt_tag, opt_class, opt_maxSearchSteps) { - if (!opt_tag && !opt_class) { - return null; - } - var tagName = opt_tag ? opt_tag.toUpperCase() : null; - return /** @type {Element} */ (goog.dom.getAncestor(element, function(node) { - return (!tagName || node.nodeName == tagName) && - (!opt_class || - goog.isString(node.className) && - goog.array.contains(node.className.split(/\s+/), opt_class)); - }, true, opt_maxSearchSteps)); -}; +ol.has.POINTER = 'PointerEvent' in ol.global; /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * class name. If the passed element matches the specified criteria, the - * element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {string} className The class name to match. - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if none match. + * True if browser supports ms pointer events (IE 10). + * @const + * @type {boolean} */ -goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) { - return goog.dom.getAncestorByTagNameAndClass( - element, null, className, opt_maxSearchSteps); -}; +ol.has.MSPOINTER = !!(ol.global.navigator.msPointerEnabled); /** - * Walks up the DOM hierarchy returning the first ancestor that passes the - * matcher function. - * @param {Node} element The DOM node to start with. - * @param {function(Node) : boolean} matcher A function that returns true if the - * passed node matches the desired criteria. - * @param {boolean=} opt_includeNode If true, the node itself is included in - * the search (the first call to the matcher will pass startElement as - * the node to test). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Node} DOM node that matched the matcher, or null if there was - * no match. + * True if both OpenLayers and browser support WebGL. Always `false` + * if `ol.ENABLE_WEBGL` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable */ -goog.dom.getAncestor = function( - element, matcher, opt_includeNode, opt_maxSearchSteps) { - if (!opt_includeNode) { - element = element.parentNode; - } - var steps = 0; - while (element && - (opt_maxSearchSteps == null || steps <= opt_maxSearchSteps)) { - goog.asserts.assert(element.name != 'parentNode'); - if (matcher(element)) { - return element; +ol.has.WEBGL; + + +(function() { + if (ol.ENABLE_WEBGL) { + var hasWebGL = false; + var textureSize; + var /** @type {Array.} */ extensions = []; + + if ('WebGLRenderingContext' in ol.global) { + try { + var canvas = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + var gl = ol.webgl.getContext(canvas, { + failIfMajorPerformanceCaveat: true + }); + if (gl) { + hasWebGL = true; + textureSize = /** @type {number} */ + (gl.getParameter(gl.MAX_TEXTURE_SIZE)); + extensions = gl.getSupportedExtensions(); + } + } catch (e) { + // pass + } } - element = element.parentNode; - steps++; + ol.has.WEBGL = hasWebGL; + ol.WEBGL_EXTENSIONS = extensions; + ol.WEBGL_MAX_TEXTURE_SIZE = textureSize; } - // Reached the root of the DOM without a match - return null; -}; +})(); + +goog.provide('ol.pointer.EventSource'); /** - * Determines the active element in the given document. - * @param {Document} doc The document to look in. - * @return {Element} The active element. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @param {!Object.} mapping Event + * mapping. + * @constructor */ -goog.dom.getActiveElement = function(doc) { - try { - return doc && doc.activeElement; - } catch (e) { - // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE - // throws an exception. I'm not 100% sure why, but I suspect it chokes - // on document.activeElement if the activeElement has been recently - // removed from the DOM by a JS operation. - // - // We assume that an exception here simply means - // "there is no active element." - } +ol.pointer.EventSource = function(dispatcher, mapping) { + /** + * @type {ol.pointer.PointerEventHandler} + */ + this.dispatcher = dispatcher; - return null; + /** + * @private + * @const + * @type {!Object.} + */ + this.mapping_ = mapping; }; /** - * Gives the current devicePixelRatio. - * - * By default, this is the value of window.devicePixelRatio (which should be - * preferred if present). - * - * If window.devicePixelRatio is not present, the ratio is calculated with - * window.matchMedia, if present. Otherwise, gives 1.0. - * - * Some browsers (including Chrome) consider the browser zoom level in the pixel - * ratio, so the value may change across multiple calls. - * - * @return {number} The number of actual pixels per virtual pixel. + * List of events supported by this source. + * @return {Array.} Event names */ -goog.dom.getPixelRatio = function() { - var win = goog.dom.getWindow(); - if (goog.isDef(win.devicePixelRatio)) { - return win.devicePixelRatio; - } else if (win.matchMedia) { - return goog.dom.matchesPixelRatio_(.75) || - goog.dom.matchesPixelRatio_(1.5) || goog.dom.matchesPixelRatio_(2) || - goog.dom.matchesPixelRatio_(3) || 1; - } - return 1; +ol.pointer.EventSource.prototype.getEvents = function() { + return Object.keys(this.mapping_); }; /** - * Calculates a mediaQuery to check if the current device supports the - * given actual to virtual pixel ratio. - * @param {number} pixelRatio The ratio of actual pixels to virtual pixels. - * @return {number} pixelRatio if applicable, otherwise 0. - * @private + * Returns a mapping between the supported event types and + * the handlers that should handle an event. + * @return {Object.} + * Event/Handler mapping */ -goog.dom.matchesPixelRatio_ = function(pixelRatio) { - var win = goog.dom.getWindow(); - var query = - ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' + - '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' + - '(min-resolution: ' + pixelRatio + 'dppx)'); - return win.matchMedia(query).matches ? pixelRatio : 0; +ol.pointer.EventSource.prototype.getMapping = function() { + return this.mapping_; }; - /** - * Create an instance of a DOM helper with a new document object. - * @param {Document=} opt_document Document object to associate with this - * DOM helper. - * @constructor + * Returns the handler that should handle a given event type. + * @param {string} eventType The event type. + * @return {function(Event)} Handler */ -goog.dom.DomHelper = function(opt_document) { - /** - * Reference to the document object to use - * @type {!Document} - * @private - */ - this.document_ = opt_document || goog.global.document || document; +ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) { + return this.mapping_[eventType]; }; +// Based on https://github.com/Polymer/PointerEvents -/** - * Gets the dom helper object for the document where the element resides. - * @param {Node=} opt_node If present, gets the DomHelper for this node. - * @return {!goog.dom.DomHelper} The DomHelper. - */ -goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper; +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +goog.provide('ol.pointer.MouseSource'); -/** - * Sets the document object. - * @param {!Document} document Document object. - */ -goog.dom.DomHelper.prototype.setDocument = function(document) { - this.document_ = document; -}; +goog.require('ol.pointer.EventSource'); /** - * Gets the document object being used by the dom library. - * @return {!Document} Document object. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} */ -goog.dom.DomHelper.prototype.getDocument = function() { - return this.document_; +ol.pointer.MouseSource = function(dispatcher) { + var mapping = { + 'mousedown': this.mousedown, + 'mousemove': this.mousemove, + 'mouseup': this.mouseup, + 'mouseover': this.mouseover, + 'mouseout': this.mouseout + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {Array.} + */ + this.lastTouches = []; }; +ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource); /** - * Alias for {@code getElementById}. If a DOM node is passed in then we just - * return that. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. + * @const + * @type {number} */ -goog.dom.DomHelper.prototype.getElement = function(element) { - return goog.dom.getElementHelper_(this.document_, element); -}; +ol.pointer.MouseSource.POINTER_ID = 1; /** - * Gets an element by id, asserting that the element is found. - * - * This is used when an element is expected to exist, and should fail with - * an assertion error if it does not (if assertions are enabled). - * - * @param {string} id Element ID. - * @return {!Element} The element with the given ID, if it exists. + * @const + * @type {string} */ -goog.dom.DomHelper.prototype.getRequiredElement = function(id) { - return goog.dom.getRequiredElementHelper_(this.document_, id); -}; +ol.pointer.MouseSource.POINTER_TYPE = 'mouse'; /** - * Alias for {@code getElement}. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. - * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead. + * Radius around touchend that swallows mouse events. + * + * @const + * @type {number} */ -goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement; +ol.pointer.MouseSource.DEDUP_DIST = 25; /** - * Looks up elements by both tag and class name, using browser native functions - * ({@code querySelectorAll}, {@code getElementsByTagName} or - * {@code getElementsByClassName}) where possible. The returned array is a live - * NodeList or a static list depending on the code path taken. + * Detect if a mouse event was simulated from a touch by + * checking if previously there was a touch event at the + * same position. * - * @see goog.dom.query + * FIXME - Known problem with the native Android browser on + * Samsung GT-I9100 (Android 4.1.2): + * In case the page is scrolled, this function does not work + * correctly when a canvas is used (WebGL or canvas renderer). + * Mouse listeners on canvas elements (for this browser), create + * two mouse events: One 'good' and one 'bad' one (on other browsers or + * when a div is used, there is only one event). For the 'bad' one, + * clientX/clientY and also pageX/pageY are wrong when the page + * is scrolled. Because of that, this function can not detect if + * the events were simulated from a touch event. As result, a + * pointer event at a wrong position is dispatched, which confuses + * the map interactions. + * It is unclear, how one can get the correct position for the event + * or detect that the positions are invalid. * - * @param {?string=} opt_tag Element tag name or * for all tags. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return {!IArrayLike} Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * @private + * @param {Event} inEvent The in event. + * @return {boolean} True, if the event was generated by a touch. */ -goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function( - opt_tag, opt_class, opt_el) { - return goog.dom.getElementsByTagNameAndClass_( - this.document_, opt_tag, opt_class, opt_el); +ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) { + var lts = this.lastTouches; + var x = inEvent.clientX, y = inEvent.clientY; + for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { + // simulated mouse events will be swallowed near a primary touchend + var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]); + if (dx <= ol.pointer.MouseSource.DEDUP_DIST && + dy <= ol.pointer.MouseSource.DEDUP_DIST) { + return true; + } + } + return false; }; /** - * Returns an array of all the elements with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {Element|Document=} opt_el Optional element to look in. - * @return {!IArrayLike} The items found with the class name provided. + * Creates a copy of the original event that will be used + * for the fake pointer event. + * + * @param {Event} inEvent The in event. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @return {Object} The copied event. */ -goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) { - var doc = opt_el || this.document_; - return goog.dom.getElementsByClass(className, doc); -}; +ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) { + var e = dispatcher.cloneEvent(inEvent, inEvent); + // forward mouse preventDefault + var pd = e.preventDefault; + e.preventDefault = function() { + inEvent.preventDefault(); + pd(); + }; -/** - * Returns the first element we find matching the provided class name. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(Element|Document)=} opt_el Optional element to look in. - * @return {Element} The first item found with the class name provided. - */ -goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) { - var doc = opt_el || this.document_; - return goog.dom.getElementByClass(className, doc); + e.pointerId = ol.pointer.MouseSource.POINTER_ID; + e.isPrimary = true; + e.pointerType = ol.pointer.MouseSource.POINTER_TYPE; + + return e; }; /** - * Ensures an element with the given className exists, and then returns the - * first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(!Element|!Document)=} opt_root Optional element or document to look - * in. - * @return {!Element} The first item found with the class name provided. - * @throws {goog.asserts.AssertionError} Thrown if no element is found. + * Handler for `mousedown`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getRequiredElementByClass = function( - className, opt_root) { - var root = opt_root || this.document_; - return goog.dom.getRequiredElementByClass(className, root); +ol.pointer.MouseSource.prototype.mousedown = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + // TODO(dfreedman) workaround for some elements not sending mouseup + // http://crbug/149091 + if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) { + this.cancel(inEvent); + } + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent; + this.dispatcher.down(e, inEvent); + } }; /** - * Alias for {@code getElementsByTagNameAndClass}. - * @deprecated Use DomHelper getElementsByTagNameAndClass. - * @see goog.dom.query + * Handler for `mousemove`. * - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {Element=} opt_el Optional element to look in. - * @return {!IArrayLike} Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.$$ = - goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; +ol.pointer.MouseSource.prototype.mousemove = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.move(e, inEvent); + } +}; /** - * Sets a number of properties on a node. - * @param {Element} element DOM node to set properties on. - * @param {Object} properties Hash of property:value pairs. + * Handler for `mouseup`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties; - +ol.pointer.MouseSource.prototype.mouseup = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; -/** - * Gets the dimensions of the viewport. - * @param {Window=} opt_window Optional window element to test. Defaults to - * the window of the Dom Helper. - * @return {!goog.math.Size} Object with values 'width' and 'height'. - */ -goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) { - // TODO(arv): This should not take an argument. That breaks the rule of a - // a DomHelper representing a single frame/window/document. - return goog.dom.getViewportSize(opt_window || this.getWindow()); + if (p && p.button === inEvent.button) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.up(e, inEvent); + this.cleanupMouse(); + } + } }; /** - * Calculates the height of the document. + * Handler for `mouseover`. * - * @return {number} The height of the document. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getDocumentHeight = function() { - return goog.dom.getDocumentHeight_(this.getWindow()); +ol.pointer.MouseSource.prototype.mouseover = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.enterOver(e, inEvent); + } }; /** - * Typedef for use with goog.dom.createDom and goog.dom.append. - * @typedef {Object|string|Array|NodeList} + * Handler for `mouseout`. + * + * @param {Event} inEvent The in event. */ -goog.dom.Appendable; +ol.pointer.MouseSource.prototype.mouseout = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.leaveOut(e, inEvent); + } +}; /** - * Returns a dom node with a set of attributes. This function accepts varargs - * for subsequent nodes to be added. Subsequent nodes will be added to the - * first node as childNodes. - * - * So: - * createDom('div', null, createDom('p'), createDom('p')); - * would return a div with two child paragraphs - * - * An easy way to move all child nodes of an existing element to a new parent - * element is: - * createDom('div', null, oldElement.childNodes); - * which will remove all child nodes from the old element and add them as - * child nodes of the new DIV. + * Dispatches a `pointercancel` event. * - * @param {string} tagName Tag to create. - * @param {Object|string=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...goog.dom.Appendable} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array or - * NodeList, its elements will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.createDom = function( - tagName, opt_attributes, var_args) { - return goog.dom.createDom_(this.document_, arguments); +ol.pointer.MouseSource.prototype.cancel = function(inEvent) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.cancel(e, inEvent); + this.cleanupMouse(); }; /** - * Alias for {@code createDom}. - * @param {string} tagName Tag to create. - * @param {(Object|string)=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for - * text nodes. If one of the var_args is an array, its children will be - * added as childNodes instead. - * @return {!Element} Reference to a DOM node. - * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead. + * Remove the mouse from the list of active pointers. */ -goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom; +ol.pointer.MouseSource.prototype.cleanupMouse = function() { + delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; +}; +// Based on https://github.com/Polymer/PointerEvents -/** - * Creates a new element. - * @param {string} name Tag name. - * @return {!Element} The new element. - */ -goog.dom.DomHelper.prototype.createElement = function(name) { - return this.document_.createElement(name); -}; +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +goog.provide('ol.pointer.MsSource'); -/** - * Creates a new text node. - * @param {number|string} content Content. - * @return {!Text} The new text node. - */ -goog.dom.DomHelper.prototype.createTextNode = function(content) { - return this.document_.createTextNode(String(content)); -}; +goog.require('ol.pointer.EventSource'); /** - * Create a table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean=} opt_fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!HTMLElement} The created table. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} */ -goog.dom.DomHelper.prototype.createTable = function( - rows, columns, opt_fillWithNbsp) { - return goog.dom.createTable_( - this.document_, rows, columns, !!opt_fillWithNbsp); -}; +ol.pointer.MsSource = function(dispatcher) { + var mapping = { + 'MSPointerDown': this.msPointerDown, + 'MSPointerMove': this.msPointerMove, + 'MSPointerUp': this.msPointerUp, + 'MSPointerOut': this.msPointerOut, + 'MSPointerOver': this.msPointerOver, + 'MSPointerCancel': this.msPointerCancel, + 'MSGotPointerCapture': this.msGotPointerCapture, + 'MSLostPointerCapture': this.msLostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + /** + * @const + * @type {!Object.} + */ + this.pointerMap = dispatcher.pointerMap; -/** - * Converts an HTML into a node or a document fragment. A single Node is used if - * {@code html} only generates a single node. If {@code html} generates multiple - * nodes then these are put inside a {@code DocumentFragment}. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. - */ -goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) { - return goog.dom.safeHtmlToNode_(this.document_, html); + /** + * @const + * @type {Array.} + */ + this.POINTER_TYPES = [ + '', + 'unavailable', + 'touch', + 'pen', + 'mouse' + ]; }; +ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource); /** - * Converts an HTML string into a node or a document fragment. A single Node - * is used if the {@code htmlString} only generates a single node. If the - * {@code htmlString} generates multiple nodes then these are put inside a - * {@code DocumentFragment}. + * Creates a copy of the original event that will be used + * for the fake pointer event. * - * @param {string} htmlString The HTML string to convert. - * @return {!Node} The resulting node. - * @deprecated Use {@link goog.dom.DomHelper.prototype.safeHtmlToNode} instead. + * @private + * @param {Event} inEvent The in event. + * @return {Object} The copied event. */ -goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) { - return goog.dom.safeHtmlToNode_(this.document_, - goog.html.legacyconversions.safeHtmlFromString(htmlString)); -}; - +ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) { + var e = inEvent; + if (goog.isNumber(inEvent.pointerType)) { + e = this.dispatcher.cloneEvent(inEvent, inEvent); + e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; + } -/** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @return {boolean} True if in CSS1-compatible mode. - */ -goog.dom.DomHelper.prototype.isCss1CompatMode = function() { - return goog.dom.isCss1CompatMode_(this.document_); + return e; }; /** - * Gets the window object associated with the document. - * @return {!Window} The window associated with the given document. + * Remove this pointer from the list of active pointers. + * @param {number} pointerId Pointer identifier. */ -goog.dom.DomHelper.prototype.getWindow = function() { - return goog.dom.getWindow_(this.document_); +ol.pointer.MsSource.prototype.cleanup = function(pointerId) { + delete this.pointerMap[pointerId.toString()]; }; /** - * Gets the document scroll element. - * @return {!Element} Scrolling element. + * Handler for `msPointerDown`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getDocumentScrollElement = function() { - return goog.dom.getDocumentScrollElement_(this.document_); +ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) { + this.pointerMap[inEvent.pointerId.toString()] = inEvent; + var e = this.prepareEvent_(inEvent); + this.dispatcher.down(e, inEvent); }; /** - * Gets the document scroll distance as a coordinate object. - * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'. + * Handler for `msPointerMove`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getDocumentScroll = function() { - return goog.dom.getDocumentScroll_(this.document_); +ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.move(e, inEvent); }; /** - * Determines the active element in the given document. - * @param {Document=} opt_doc The document to look in. - * @return {Element} The active element. + * Handler for `msPointerUp`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) { - return goog.dom.getActiveElement(opt_doc || this.document_); +ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.up(e, inEvent); + this.cleanup(inEvent.pointerId); }; /** - * Appends a child to a node. - * @param {Node} parent Parent. - * @param {Node} child Child. - */ -goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild; - - -/** - * Appends a node with text or other nodes. - * @param {!Node} parent The node to append nodes to. - * @param {...goog.dom.Appendable} var_args The things to append to the node. - * If this is a Node it is appended as is. - * If this is a string then a text node is appended. - * If this is an array like object then fields 0 to length - 1 are appended. + * Handler for `msPointerOut`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.append = goog.dom.append; +ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.leaveOut(e, inEvent); +}; /** - * Determines if the given node can contain children, intended to be used for - * HTML generation. + * Handler for `msPointerOver`. * - * @param {Node} node The node to check. - * @return {boolean} Whether the node can contain children. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren; +ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.enterOver(e, inEvent); +}; /** - * Removes all the child nodes on a DOM node. - * @param {Node} node Node to remove children from. + * Handler for `msPointerCancel`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren; +ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.cancel(e, inEvent); + this.cleanup(inEvent.pointerId); +}; /** - * Inserts a new node before an existing reference node (i.e., as the previous - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert before. + * Handler for `msLostPointerCapture`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore; +ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('lostpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; /** - * Inserts a new node after an existing reference node (i.e., as the next - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert after. + * Handler for `msGotPointerCapture`. + * + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter; - +ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('gotpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; -/** - * Insert a child at a given index. If index is larger than the number of child - * nodes that the parent currently has, the node is inserted as the last child - * node. - * @param {Element} parent The element into which to insert the child. - * @param {Node} child The element to insert. - * @param {number} index The index at which to insert the new child node. Must - * not be negative. - */ -goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt; +// Based on https://github.com/Polymer/PointerEvents +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -/** - * Removes a node from its parent. - * @param {Node} node The node to remove. - * @return {Node} The node removed if removed; else, null. - */ -goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode; - - -/** - * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no - * parent. - * @param {Node} newNode Node to insert. - * @param {Node} oldNode Node to replace. - */ -goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode; - - -/** - * Flattens an element. That is, removes it and replace it with its children. - * @param {Element} element The element to flatten. - * @return {Element|undefined} The original element, detached from the document - * tree, sans children, or undefined if the element was already not in the - * document. - */ -goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement; - - -/** - * Returns an array containing just the element children of the given element. - * @param {Element} element The element whose element children we want. - * @return {!(Array|NodeList)} An array or array-like list - * of just the element children of the given element. - */ -goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren; - - -/** - * Returns the first child node that is an element. - * @param {Node} node The node to get the first child element of. - * @return {Element} The first child node of {@code node} that is an element. - */ -goog.dom.DomHelper.prototype.getFirstElementChild = - goog.dom.getFirstElementChild; - - -/** - * Returns the last child node that is an element. - * @param {Node} node The node to get the last child element of. - * @return {Element} The last child node of {@code node} that is an element. - */ -goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild; - - -/** - * Returns the first next sibling that is an element. - * @param {Node} node The node to get the next sibling element of. - * @return {Element} The next sibling of {@code node} that is an element. - */ -goog.dom.DomHelper.prototype.getNextElementSibling = - goog.dom.getNextElementSibling; - - -/** - * Returns the first previous sibling that is an element. - * @param {Node} node The node to get the previous sibling element of. - * @return {Element} The first previous sibling of {@code node} that is - * an element. - */ -goog.dom.DomHelper.prototype.getPreviousElementSibling = - goog.dom.getPreviousElementSibling; - - -/** - * Returns the next node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The next node in the DOM tree, or null if this was the last - * node. - */ -goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode; - - -/** - * Returns the previous node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The previous node in the DOM tree, or null if this was the - * first node. - */ -goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode; - - -/** - * Whether the object looks like a DOM node. - * @param {?} obj The object being tested for node likeness. - * @return {boolean} Whether the object looks like a DOM node. - */ -goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike; - - -/** - * Whether the object looks like an Element. - * @param {?} obj The object being tested for Element likeness. - * @return {boolean} Whether the object looks like an Element. - */ -goog.dom.DomHelper.prototype.isElement = goog.dom.isElement; - - -/** - * Returns true if the specified value is a Window object. This includes the - * global window for HTML pages, and iframe windows. - * @param {?} obj Variable to test. - * @return {boolean} Whether the variable is a window. - */ -goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow; - +goog.provide('ol.pointer.NativeSource'); -/** - * Returns an element's parent, if it's an Element. - * @param {Element} element The DOM element. - * @return {Element} The parent, or null if not an Element. - */ -goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement; +goog.require('ol.pointer.EventSource'); /** - * Whether a node contains another node. - * @param {Node} parent The node that should contain the other node. - * @param {Node} descendant The node to test presence of. - * @return {boolean} Whether the parent node contains the descendent node. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} */ -goog.dom.DomHelper.prototype.contains = goog.dom.contains; +ol.pointer.NativeSource = function(dispatcher) { + var mapping = { + 'pointerdown': this.pointerDown, + 'pointermove': this.pointerMove, + 'pointerup': this.pointerUp, + 'pointerout': this.pointerOut, + 'pointerover': this.pointerOver, + 'pointercancel': this.pointerCancel, + 'gotpointercapture': this.gotPointerCapture, + 'lostpointercapture': this.lostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); +}; +ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource); /** - * Compares the document order of two nodes, returning 0 if they are the same - * node, a negative number if node1 is before node2, and a positive number if - * node2 is before node1. Note that we compare the order the tags appear in the - * document so in the tree text the B node is considered to be - * before the I node. + * Handler for `pointerdown`. * - * @param {Node} node1 The first node to compare. - * @param {Node} node2 The second node to compare. - * @return {number} 0 if the nodes are the same node, a negative number if node1 - * is before node2, and a positive number if node2 is before node1. - */ -goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder; - - -/** - * Find the deepest common ancestor of the given nodes. - * @param {...Node} var_args The nodes to find a common ancestor of. - * @return {Node} The common ancestor of the nodes, or null if there is none. - * null will only be returned if two or more of the nodes are from different - * documents. - */ -goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor; - - -/** - * Returns the owner document for a node. - * @param {Node} node The node to get the document for. - * @return {!Document} The document owning the node. - */ -goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument; - - -/** - * Cross browser function for getting the document element of an iframe. - * @param {Element} iframe Iframe element. - * @return {!Document} The frame content document. - */ -goog.dom.DomHelper.prototype.getFrameContentDocument = - goog.dom.getFrameContentDocument; - - -/** - * Cross browser function for getting the window of a frame or iframe. - * @param {Element} frame Frame element. - * @return {Window} The window associated with the given frame. - */ -goog.dom.DomHelper.prototype.getFrameContentWindow = - goog.dom.getFrameContentWindow; - - -/** - * Sets the text content of a node, with cross-browser support. - * @param {Node} node The node to change the text content of. - * @param {string|number} text The value that should replace the node's content. - */ -goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent; - - -/** - * Gets the outerHTML of a node, which islike innerHTML, except that it - * actually contains the HTML of the node itself. - * @param {Element} element The element to get the HTML of. - * @return {string} The outerHTML of the given element. - */ -goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml; - - -/** - * Finds the first descendant node that matches the filter function. This does - * a depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Node|undefined} The found node or undefined if none is found. - */ -goog.dom.DomHelper.prototype.findNode = goog.dom.findNode; - - -/** - * Finds all the descendant nodes that matches the filter function. This does a - * depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Array} The found nodes or an empty array if none are found. - */ -goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes; - - -/** - * Returns true if the element has a tab index that allows it to receive - * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements - * natively support keyboard focus, even if they have no tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a tab index that allows keyboard - * focus. - */ -goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex; - - -/** - * Enables or disables keyboard focus support on the element via its tab index. - * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true - * (or elements that natively support keyboard focus, like form elements) can - * receive keyboard focus. See http://go/tabindex for more info. - * @param {Element} element Element whose tab index is to be changed. - * @param {boolean} enable Whether to set or remove a tab index on the element - * that supports keyboard focus. - */ -goog.dom.DomHelper.prototype.setFocusableTabIndex = - goog.dom.setFocusableTabIndex; - - -/** - * Returns true if the element can be focused, i.e. it has a tab index that - * allows it to receive keyboard focus (tabIndex >= 0), or it is an element - * that natively supports keyboard focus. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element allows keyboard focus. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable; +ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; /** - * Returns the text contents of the current node, without markup. New lines are - * stripped and whitespace is collapsed, such that each character would be - * visible. - * - * In browsers that support it, innerText is used. Other browsers attempt to - * simulate it via node traversal. Line breaks are canonicalized in IE. + * Handler for `pointermove`. * - * @param {Node} node The node from which we are getting content. - * @return {string} The text content. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent; +ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; /** - * Returns the text length of the text contained in a node, without markup. This - * is equivalent to the selection length if the node was selected, or the number - * of cursor movements to traverse the node. Images & BRs take one space. New - * lines are ignored. + * Handler for `pointerup`. * - * @param {Node} node The node whose text content length is being calculated. - * @return {number} The length of {@code node}'s text content. + * @param {Event} inEvent The in event. */ -goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength; +ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; /** - * Returns the text offset of a node relative to one of its ancestors. The text - * length is the same as the length calculated by - * {@code goog.dom.getNodeTextLength}. + * Handler for `pointerout`. * - * @param {Node} node The node whose offset is being calculated. - * @param {Node=} opt_offsetParent Defaults to the node's owner document's body. - * @return {number} The text offset. - */ -goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset; - - -/** - * Returns the node at a given offset in a parent node. If an object is - * provided for the optional third parameter, the node and the remainder of the - * offset will stored as properties of this object. - * @param {Node} parent The parent node. - * @param {number} offset The offset into the parent node. - * @param {Object=} opt_result Object to be used to store the return value. The - * return value will be stored in the form {node: Node, remainder: number} - * if this object is provided. - * @return {Node} The node at the given offset. - */ -goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset; - - -/** - * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, - * the object must have a numeric length property and an item function (which - * has type 'string' on IE for some reason). - * @param {Object} val Object to test. - * @return {boolean} Whether the object is a NodeList. - */ -goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList; - - -/** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * tag name and/or class name. If the passed element matches the specified - * criteria, the element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or - * null/undefined to match only based on class name). - * @param {?string=} opt_class The class name to match (or null/undefined to - * match only based on tag name). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if no match is found. - */ -goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = - goog.dom.getAncestorByTagNameAndClass; - - -/** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * class name. If the passed element matches the specified criteria, the - * element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {string} class The class name to match. - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if none match. - */ -goog.dom.DomHelper.prototype.getAncestorByClass = goog.dom.getAncestorByClass; - - -/** - * Walks up the DOM hierarchy returning the first ancestor that passes the - * matcher function. - * @param {Node} element The DOM node to start with. - * @param {function(Node) : boolean} matcher A function that returns true if the - * passed node matches the desired criteria. - * @param {boolean=} opt_includeNode If true, the node itself is included in - * the search (the first call to the matcher will pass startElement as - * the node to test). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Node} DOM node that matched the matcher, or null if there was - * no match. - */ -goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor; - -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Vendor prefix getters. + * @param {Event} inEvent The in event. */ - -goog.provide('goog.dom.vendor'); - -goog.require('goog.string'); -goog.require('goog.userAgent'); +ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; /** - * Returns the JS vendor prefix used in CSS properties. Different vendors - * use different methods of changing the case of the property names. + * Handler for `pointerover`. * - * @return {?string} The JS vendor prefix or null if there is none. + * @param {Event} inEvent The in event. */ -goog.dom.vendor.getVendorJsPrefix = function() { - if (goog.userAgent.WEBKIT) { - return 'Webkit'; - } else if (goog.userAgent.GECKO) { - return 'Moz'; - } else if (goog.userAgent.IE) { - return 'ms'; - } else if (goog.userAgent.OPERA) { - return 'O'; - } - - return null; +ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); }; /** - * Returns the vendor prefix used in CSS properties. + * Handler for `pointercancel`. * - * @return {?string} The vendor prefix or null if there is none. + * @param {Event} inEvent The in event. */ -goog.dom.vendor.getVendorPrefix = function() { - if (goog.userAgent.WEBKIT) { - return '-webkit'; - } else if (goog.userAgent.GECKO) { - return '-moz'; - } else if (goog.userAgent.IE) { - return '-ms'; - } else if (goog.userAgent.OPERA) { - return '-o'; - } - - return null; +ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); }; /** - * @param {string} propertyName A property name. - * @param {!Object=} opt_object If provided, we verify if the property exists in - * the object. - * @return {?string} A vendor prefixed property name, or null if it does not - * exist. - */ -goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) { - // We first check for a non-prefixed property, if available. - if (opt_object && propertyName in opt_object) { - return propertyName; - } - var prefix = goog.dom.vendor.getVendorJsPrefix(); - if (prefix) { - prefix = prefix.toLowerCase(); - var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName); - return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ? - prefixedPropertyName : - null; - } - return null; + * Handler for `lostpointercapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); }; /** - * @param {string} eventType An event type. - * @return {string} A lower-cased vendor prefixed event type. + * Handler for `gotpointercapture`. + * + * @param {Event} inEvent The in event. */ -goog.dom.vendor.getPrefixedEventType = function(eventType) { - var prefix = goog.dom.vendor.getVendorJsPrefix() || ''; - return (prefix + eventType).toLowerCase(); +ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: // -// http://www.apache.org/licenses/LICENSE-2.0 +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview A utility class for representing a numeric box. - */ - - -goog.provide('goog.math.Box'); +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -goog.require('goog.asserts'); -goog.require('goog.math.Coordinate'); +goog.provide('ol.pointer.TouchSource'); +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.pointer.EventSource'); +goog.require('ol.pointer.MouseSource'); /** - * Class for representing a box. A box is specified as a top, right, bottom, - * and left. A box is useful for representing margins and padding. - * - * This class assumes 'screen coordinates': larger Y coordinates are further - * from the top of the screen. - * - * @param {number} top Top. - * @param {number} right Right. - * @param {number} bottom Bottom. - * @param {number} left Left. - * @struct * @constructor + * @param {ol.pointer.PointerEventHandler} dispatcher The event handler. + * @param {ol.pointer.MouseSource} mouseSource Mouse source. + * @extends {ol.pointer.EventSource} */ -goog.math.Box = function(top, right, bottom, left) { +ol.pointer.TouchSource = function(dispatcher, mouseSource) { + var mapping = { + 'touchstart': this.touchstart, + 'touchmove': this.touchmove, + 'touchend': this.touchend, + 'touchcancel': this.touchcancel + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + /** - * Top - * @type {number} + * @const + * @type {!Object.} */ - this.top = top; + this.pointerMap = dispatcher.pointerMap; /** - * Right - * @type {number} + * @const + * @type {ol.pointer.MouseSource} */ - this.right = right; + this.mouseSource = mouseSource; /** - * Bottom - * @type {number} + * @private + * @type {number|undefined} */ - this.bottom = bottom; + this.firstTouchId_ = undefined; /** - * Left + * @private * @type {number} */ - this.left = left; + this.clickCount_ = 0; + + /** + * @private + * @type {number|undefined} + */ + this.resetId_ = undefined; }; +ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource); /** - * Creates a Box by bounding a collection of goog.math.Coordinate objects - * @param {...goog.math.Coordinate} var_args Coordinates to be included inside - * the box. - * @return {!goog.math.Box} A Box containing all the specified Coordinates. + * Mouse event timeout: This should be long enough to + * ignore compat mouse events made by touch. + * @const + * @type {number} */ -goog.math.Box.boundingBox = function(var_args) { - var box = new goog.math.Box( - arguments[0].y, arguments[0].x, arguments[0].y, arguments[0].x); - for (var i = 1; i < arguments.length; i++) { - box.expandToIncludeCoordinate(arguments[i]); - } - return box; -}; +ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500; /** - * @return {number} width The width of this Box. + * @const + * @type {number} */ -goog.math.Box.prototype.getWidth = function() { - return this.right - this.left; -}; +ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200; /** - * @return {number} height The height of this Box. + * @const + * @type {string} */ -goog.math.Box.prototype.getHeight = function() { - return this.bottom - this.top; -}; +ol.pointer.TouchSource.POINTER_TYPE = 'touch'; /** - * Creates a copy of the box with the same dimensions. - * @return {!goog.math.Box} A clone of this Box. + * @private + * @param {Touch} inTouch The in touch. + * @return {boolean} True, if this is the primary touch. */ -goog.math.Box.prototype.clone = function() { - return new goog.math.Box(this.top, this.right, this.bottom, this.left); +ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) { + return this.firstTouchId_ === inTouch.identifier; }; -if (goog.DEBUG) { - /** - * Returns a nice string representing the box. - * @return {string} In the form (50t, 73r, 24b, 13l). - * @override - */ - goog.math.Box.prototype.toString = function() { - return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + - this.left + 'l)'; - }; -} - - /** - * Returns whether the box contains a coordinate or another box. - * - * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. - * @return {boolean} Whether the box contains the coordinate or other box. + * Set primary touch if there are no pointers, or the only pointer is the mouse. + * @param {Touch} inTouch The in touch. + * @private */ -goog.math.Box.prototype.contains = function(other) { - return goog.math.Box.contains(this, other); +ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) { + var count = Object.keys(this.pointerMap).length; + if (count === 0 || (count === 1 && + ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) { + this.firstTouchId_ = inTouch.identifier; + this.cancelResetClickCount_(); + } }; /** - * Expands box with the given margins. - * - * @param {number|goog.math.Box} top Top margin or box with all margins. - * @param {number=} opt_right Right margin. - * @param {number=} opt_bottom Bottom margin. - * @param {number=} opt_left Left margin. - * @return {!goog.math.Box} A reference to this Box. + * @private + * @param {Object} inPointer The in pointer object. */ -goog.math.Box.prototype.expand = function( - top, opt_right, opt_bottom, opt_left) { - if (goog.isObject(top)) { - this.top -= top.top; - this.right += top.right; - this.bottom += top.bottom; - this.left -= top.left; - } else { - this.top -= /** @type {number} */ (top); - this.right += Number(opt_right); - this.bottom += Number(opt_bottom); - this.left -= Number(opt_left); +ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) { + if (inPointer.isPrimary) { + this.firstTouchId_ = undefined; + this.resetClickCount_(); } - - return this; }; /** - * Expand this box to include another box. - * NOTE(user): This is used in code that needs to be very fast, please don't - * add functionality to this function at the expense of speed (variable - * arguments, accepting multiple argument types, etc). - * @param {goog.math.Box} box The box to include in this one. + * @private */ -goog.math.Box.prototype.expandToInclude = function(box) { - this.left = Math.min(this.left, box.left); - this.top = Math.min(this.top, box.top); - this.right = Math.max(this.right, box.right); - this.bottom = Math.max(this.bottom, box.bottom); +ol.pointer.TouchSource.prototype.resetClickCount_ = function() { + this.resetId_ = ol.global.setTimeout( + this.resetClickCountHandler_.bind(this), + ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT); }; /** - * Expand this box to include the coordinate. - * @param {!goog.math.Coordinate} coord The coordinate to be included - * inside the box. + * @private */ -goog.math.Box.prototype.expandToIncludeCoordinate = function(coord) { - this.top = Math.min(this.top, coord.y); - this.right = Math.max(this.right, coord.x); - this.bottom = Math.max(this.bottom, coord.y); - this.left = Math.min(this.left, coord.x); +ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() { + this.clickCount_ = 0; + this.resetId_ = undefined; }; /** - * Compares boxes for equality. - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A Box. - * @return {boolean} True iff the boxes are equal, or if both are null. + * @private */ -goog.math.Box.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; +ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() { + if (this.resetId_ !== undefined) { + ol.global.clearTimeout(this.resetId_); } - return a.top == b.top && a.right == b.right && a.bottom == b.bottom && - a.left == b.left; }; /** - * Returns whether a box contains a coordinate or another box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. - * @return {boolean} Whether the box contains the coordinate or other box. + * @private + * @param {Event} browserEvent Browser event + * @param {Touch} inTouch Touch event + * @return {Object} A pointer object. */ -goog.math.Box.contains = function(box, other) { - if (!box || !other) { - return false; - } +ol.pointer.TouchSource.prototype.touchToPointer_ = function(browserEvent, inTouch) { + var e = this.dispatcher.cloneEvent(browserEvent, inTouch); + // Spec specifies that pointerId 1 is reserved for Mouse. + // Touch identifiers can start at 0. + // Add 2 to the touch identifier for compatibility. + e.pointerId = inTouch.identifier + 2; + // TODO: check if this is necessary? + //e.target = findTarget(e); + e.bubbles = true; + e.cancelable = true; + e.detail = this.clickCount_; + e.button = 0; + e.buttons = 1; + e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; + e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; + e.pressure = inTouch.webkitForce || inTouch.force || 0.5; + e.isPrimary = this.isPrimaryTouch_(inTouch); + e.pointerType = ol.pointer.TouchSource.POINTER_TYPE; - if (other instanceof goog.math.Box) { - return other.left >= box.left && other.right <= box.right && - other.top >= box.top && other.bottom <= box.bottom; - } + // make sure that the properties that are different for + // each `Touch` object are not copied from the BrowserEvent object + e.clientX = inTouch.clientX; + e.clientY = inTouch.clientY; + e.screenX = inTouch.screenX; + e.screenY = inTouch.screenY; - // other is a Coordinate. - return other.x >= box.left && other.x <= box.right && other.y >= box.top && - other.y <= box.bottom; + return e; }; /** - * Returns the relative x position of a coordinate compared to a box. Returns - * zero if the coordinate is inside the box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The x position of {@code coord} relative to the nearest - * side of {@code box}, or zero if {@code coord} is inside {@code box}. + * @private + * @param {Event} inEvent Touch event + * @param {function(Event, Object)} inFunction In function. */ -goog.math.Box.relativePositionX = function(box, coord) { - if (coord.x < box.left) { - return coord.x - box.left; - } else if (coord.x > box.right) { - return coord.x - box.right; +ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) { + var touches = Array.prototype.slice.call( + inEvent.changedTouches); + var count = touches.length; + function preventDefault() { + inEvent.preventDefault(); + } + var i, pointer; + for (i = 0; i < count; ++i) { + pointer = this.touchToPointer_(inEvent, touches[i]); + // forward touch preventDefaults + pointer.preventDefault = preventDefault; + inFunction.call(this, inEvent, pointer); } - return 0; }; /** - * Returns the relative y position of a coordinate compared to a box. Returns - * zero if the coordinate is inside the box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The y position of {@code coord} relative to the nearest - * side of {@code box}, or zero if {@code coord} is inside {@code box}. + * @private + * @param {TouchList} touchList The touch list. + * @param {number} searchId Search identifier. + * @return {boolean} True, if the `Touch` with the given id is in the list. */ -goog.math.Box.relativePositionY = function(box, coord) { - if (coord.y < box.top) { - return coord.y - box.top; - } else if (coord.y > box.bottom) { - return coord.y - box.bottom; +ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) { + var l = touchList.length; + var touch; + for (var i = 0; i < l; i++) { + touch = touchList[i]; + if (touch.identifier === searchId) { + return true; + } } - return 0; + return false; }; /** - * Returns the distance between a coordinate and the nearest corner/side of a - * box. Returns zero if the coordinate is inside the box. + * In some instances, a touchstart can happen without a touchend. This + * leaves the pointermap in a broken state. + * Therefore, on every touchstart, we remove the touches that did not fire a + * touchend event. + * To keep state globally consistent, we fire a pointercancel for + * this "abandoned" touch * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The distance between {@code coord} and the nearest - * corner/side of {@code box}, or zero if {@code coord} is inside - * {@code box}. + * @private + * @param {Event} inEvent The in event. */ -goog.math.Box.distance = function(box, coord) { - var x = goog.math.Box.relativePositionX(box, coord); - var y = goog.math.Box.relativePositionY(box, coord); - return Math.sqrt(x * x + y * y); +ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) { + var touchList = inEvent.touches; + // pointerMap.getCount() should be < touchList.length here, + // as the touchstart has not been processed yet. + var keys = Object.keys(this.pointerMap); + var count = keys.length; + if (count >= touchList.length) { + var d = []; + var i, key, value; + for (i = 0; i < count; ++i) { + key = keys[i]; + value = this.pointerMap[key]; + // Never remove pointerId == 1, which is mouse. + // Touch identifiers are 2 smaller than their pointerId, which is the + // index in pointermap. + if (key != ol.pointer.MouseSource.POINTER_ID && + !this.findTouch_(touchList, key - 2)) { + d.push(value.out); + } + } + for (i = 0; i < d.length; ++i) { + this.cancelOut_(inEvent, d[i]); + } + } }; /** - * Returns whether two boxes intersect. + * Handler for `touchstart`, triggers `pointerover`, + * `pointerenter` and `pointerdown` events. * - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A second Box. - * @return {boolean} Whether the boxes intersect. + * @param {Event} inEvent The in event. */ -goog.math.Box.intersects = function(a, b) { - return ( - a.left <= b.right && b.left <= a.right && a.top <= b.bottom && - b.top <= a.bottom); +ol.pointer.TouchSource.prototype.touchstart = function(inEvent) { + this.vacuumTouches_(inEvent); + this.setPrimaryTouch_(inEvent.changedTouches[0]); + this.dedupSynthMouse_(inEvent); + this.clickCount_++; + this.processTouches_(inEvent, this.overDown_); }; /** - * Returns whether two boxes would intersect with additional padding. - * - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A second Box. - * @param {number} padding The additional padding. - * @return {boolean} Whether the boxes intersect. + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer object. */ -goog.math.Box.intersectsWithPadding = function(a, b, padding) { - return ( - a.left <= b.right + padding && b.left <= a.right + padding && - a.top <= b.bottom + padding && b.top <= a.bottom + padding); +ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) { + this.pointerMap[inPointer.pointerId] = { + target: inPointer.target, + out: inPointer, + outTarget: inPointer.target + }; + this.dispatcher.over(inPointer, browserEvent); + this.dispatcher.enter(inPointer, browserEvent); + this.dispatcher.down(inPointer, browserEvent); }; /** - * Rounds the fields to the next larger integer values. + * Handler for `touchmove`. * - * @return {!goog.math.Box} This box with ceil'd fields. + * @param {Event} inEvent The in event. */ -goog.math.Box.prototype.ceil = function() { - this.top = Math.ceil(this.top); - this.right = Math.ceil(this.right); - this.bottom = Math.ceil(this.bottom); - this.left = Math.ceil(this.left); - return this; +ol.pointer.TouchSource.prototype.touchmove = function(inEvent) { + inEvent.preventDefault(); + this.processTouches_(inEvent, this.moveOverOut_); }; /** - * Rounds the fields to the next smaller integer values. - * - * @return {!goog.math.Box} This box with floored fields. + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. */ -goog.math.Box.prototype.floor = function() { - this.top = Math.floor(this.top); - this.right = Math.floor(this.right); - this.bottom = Math.floor(this.bottom); - this.left = Math.floor(this.left); - return this; +ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) { + var event = inPointer; + var pointer = this.pointerMap[event.pointerId]; + // a finger drifted off the screen, ignore it + if (!pointer) { + return; + } + var outEvent = pointer.out; + var outTarget = pointer.outTarget; + this.dispatcher.move(event, browserEvent); + if (outEvent && outTarget !== event.target) { + outEvent.relatedTarget = event.target; + event.relatedTarget = outTarget; + // recover from retargeting by shadow + outEvent.target = outTarget; + if (event.target) { + this.dispatcher.leaveOut(outEvent, browserEvent); + this.dispatcher.enterOver(event, browserEvent); + } else { + // clean up case when finger leaves the screen + event.target = outTarget; + event.relatedTarget = null; + this.cancelOut_(browserEvent, event); + } + } + pointer.out = event; + pointer.outTarget = event.target; }; /** - * Rounds the fields to nearest integer values. + * Handler for `touchend`, triggers `pointerup`, + * `pointerout` and `pointerleave` events. * - * @return {!goog.math.Box} This box with rounded fields. + * @param {Event} inEvent The event. */ -goog.math.Box.prototype.round = function() { - this.top = Math.round(this.top); - this.right = Math.round(this.right); - this.bottom = Math.round(this.bottom); - this.left = Math.round(this.left); - return this; +ol.pointer.TouchSource.prototype.touchend = function(inEvent) { + this.dedupSynthMouse_(inEvent); + this.processTouches_(inEvent, this.upOut_); }; /** - * Translates this box by the given offsets. If a {@code goog.math.Coordinate} - * is given, then the left and right values are translated by the coordinate's - * x value and the top and bottom values are translated by the coordinate's y - * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x - * and y dimension values. - * - * @param {number|goog.math.Coordinate} tx The value to translate the x - * dimension values by or the the coordinate to translate this box by. - * @param {number=} opt_ty The value to translate y dimension values by. - * @return {!goog.math.Box} This box after translating. + * @private + * @param {Event} browserEvent An event. + * @param {Object} inPointer The inPointer object. */ -goog.math.Box.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.left += tx.x; - this.right += tx.x; - this.top += tx.y; - this.bottom += tx.y; - } else { - goog.asserts.assertNumber(tx); - this.left += tx; - this.right += tx; - if (goog.isNumber(opt_ty)) { - this.top += opt_ty; - this.bottom += opt_ty; - } - } - return this; +ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) { + this.dispatcher.up(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); }; /** - * Scales this coordinate by the given scale factors. The x and y dimension - * values are scaled by {@code sx} and {@code opt_sy} respectively. - * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. + * Handler for `touchcancel`, triggers `pointercancel`, + * `pointerout` and `pointerleave` events. * - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Box} This box after scaling. + * @param {Event} inEvent The in event. */ -goog.math.Box.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.left *= sx; - this.right *= sx; - this.top *= sy; - this.bottom *= sy; - return this; +ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) { + this.processTouches_(inEvent, this.cancelOut_); }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility class for representing rectangles. + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. */ - -goog.provide('goog.math.Rect'); - -goog.require('goog.asserts'); -goog.require('goog.math.Box'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Size'); - +ol.pointer.TouchSource.prototype.cancelOut_ = function(browserEvent, inPointer) { + this.dispatcher.cancel(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); +}; /** - * Class for representing rectangular regions. - * @param {number} x Left. - * @param {number} y Top. - * @param {number} w Width. - * @param {number} h Height. - * @struct - * @constructor + * @private + * @param {Object} inPointer The inPointer object. */ -goog.math.Rect = function(x, y, w, h) { - /** @type {number} */ - this.left = x; - - /** @type {number} */ - this.top = y; - - /** @type {number} */ - this.width = w; - - /** @type {number} */ - this.height = h; +ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) { + delete this.pointerMap[inPointer.pointerId]; + this.removePrimaryPointer_(inPointer); }; /** - * @return {!goog.math.Rect} A new copy of this Rectangle. + * Prevent synth mouse events from creating pointer events. + * + * @private + * @param {Event} inEvent The in event. */ -goog.math.Rect.prototype.clone = function() { - return new goog.math.Rect(this.left, this.top, this.width, this.height); +ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) { + var lts = this.mouseSource.lastTouches; + var t = inEvent.changedTouches[0]; + // only the primary finger will synth mouse events + if (this.isPrimaryTouch_(t)) { + // remember x/y of last touch + var lt = [t.clientX, t.clientY]; + lts.push(lt); + + ol.global.setTimeout(function() { + // remove touch after timeout + ol.array.remove(lts, lt); + }, ol.pointer.TouchSource.DEDUP_TIMEOUT); + } }; +// Based on https://github.com/Polymer/PointerEvents -/** - * Returns a new Box object with the same position and dimensions as this - * rectangle. - * @return {!goog.math.Box} A new Box representation of this Rectangle. - */ -goog.math.Rect.prototype.toBox = function() { - var right = this.left + this.width; - var bottom = this.top + this.height; - return new goog.math.Box(this.top, right, bottom, this.left); -}; +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +goog.provide('ol.pointer.PointerEventHandler'); -/** - * Creates a new Rect object with the position and size given. - * @param {!goog.math.Coordinate} position The top-left coordinate of the Rect - * @param {!goog.math.Size} size The size of the Rect - * @return {!goog.math.Rect} A new Rect initialized with the given position and - * size. - */ -goog.math.Rect.createFromPositionAndSize = function(position, size) { - return new goog.math.Rect(position.x, position.y, size.width, size.height); -}; +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); + +goog.require('ol.has'); +goog.require('ol.pointer.MouseSource'); +goog.require('ol.pointer.MsSource'); +goog.require('ol.pointer.NativeSource'); +goog.require('ol.pointer.PointerEvent'); +goog.require('ol.pointer.TouchSource'); /** - * Creates a new Rect object with the same position and dimensions as a given - * Box. Note that this is only the inverse of toBox if left/top are defined. - * @param {goog.math.Box} box A box. - * @return {!goog.math.Rect} A new Rect initialized with the box's position - * and size. + * @constructor + * @extends {ol.events.EventTarget} + * @param {Element|HTMLDocument} element Viewport element. */ -goog.math.Rect.createFromBox = function(box) { - return new goog.math.Rect( - box.left, box.top, box.right - box.left, box.bottom - box.top); -}; +ol.pointer.PointerEventHandler = function(element) { + ol.events.EventTarget.call(this); + /** + * @const + * @private + * @type {Element|HTMLDocument} + */ + this.element_ = element; + + /** + * @const + * @type {!Object.} + */ + this.pointerMap = {}; -if (goog.DEBUG) { /** - * Returns a nice string representing size and dimensions of rectangle. - * @return {string} In the form (50, 73 - 75w x 25h). - * @override + * @type {Object.} + * @private */ - goog.math.Rect.prototype.toString = function() { - return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' + - this.height + 'h)'; - }; -} + this.eventMap_ = {}; + /** + * @type {Array.} + * @private + */ + this.eventSourceList_ = []; -/** - * Compares rectangles for equality. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {boolean} True iff the rectangles have the same left, top, width, - * and height, or if both are null. - */ -goog.math.Rect.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; - } - return a.left == b.left && a.width == b.width && a.top == b.top && - a.height == b.height; + this.registerSources(); }; +ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget); /** - * Computes the intersection of this rectangle and the rectangle parameter. If - * there is no intersection, returns false and leaves this rectangle as is. - * @param {goog.math.Rect} rect A Rectangle. - * @return {boolean} True iff this rectangle intersects with the parameter. + * Set up the event sources (mouse, touch and native pointers) + * that generate pointer events. */ -goog.math.Rect.prototype.intersection = function(rect) { - var x0 = Math.max(this.left, rect.left); - var x1 = Math.min(this.left + this.width, rect.left + rect.width); - - if (x0 <= x1) { - var y0 = Math.max(this.top, rect.top); - var y1 = Math.min(this.top + this.height, rect.top + rect.height); - - if (y0 <= y1) { - this.left = x0; - this.top = y0; - this.width = x1 - x0; - this.height = y1 - y0; +ol.pointer.PointerEventHandler.prototype.registerSources = function() { + if (ol.has.POINTER) { + this.registerSource('native', new ol.pointer.NativeSource(this)); + } else if (ol.has.MSPOINTER) { + this.registerSource('ms', new ol.pointer.MsSource(this)); + } else { + var mouseSource = new ol.pointer.MouseSource(this); + this.registerSource('mouse', mouseSource); - return true; + if (ol.has.TOUCH) { + this.registerSource('touch', + new ol.pointer.TouchSource(this, mouseSource)); } } - return false; + + // register events on the viewport element + this.register_(); }; /** - * Returns the intersection of two rectangles. Two rectangles intersect if they - * touch at all, for example, two zero width and height rectangles would - * intersect if they had the same top and left. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {goog.math.Rect} A new intersection rect (even if width and height - * are 0), or null if there is no intersection. + * Add a new event source that will generate pointer events. + * + * @param {string} name A name for the event source + * @param {ol.pointer.EventSource} source The source event. */ -goog.math.Rect.intersection = function(a, b) { - // There is no nice way to do intersection via a clone, because any such - // clone might be unnecessary if this function returns null. So, we duplicate - // code from above. - - var x0 = Math.max(a.left, b.left); - var x1 = Math.min(a.left + a.width, b.left + b.width); +ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) { + var s = source; + var newEvents = s.getEvents(); - if (x0 <= x1) { - var y0 = Math.max(a.top, b.top); - var y1 = Math.min(a.top + a.height, b.top + b.height); + if (newEvents) { + newEvents.forEach(function(e) { + var handler = s.getHandlerForEvent(e); - if (y0 <= y1) { - return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0); - } + if (handler) { + this.eventMap_[e] = handler.bind(s); + } + }, this); + this.eventSourceList_.push(s); } - return null; }; /** - * Returns whether two rectangles intersect. Two rectangles intersect if they - * touch at all, for example, two zero width and height rectangles would - * intersect if they had the same top and left. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {boolean} Whether a and b intersect. + * Set up the events for all registered event sources. + * @private */ -goog.math.Rect.intersects = function(a, b) { - return ( - a.left <= b.left + b.width && b.left <= a.left + a.width && - a.top <= b.top + b.height && b.top <= a.top + a.height); +ol.pointer.PointerEventHandler.prototype.register_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.addEvents_(eventSource.getEvents()); + } }; /** - * Returns whether a rectangle intersects this rectangle. - * @param {goog.math.Rect} rect A rectangle. - * @return {boolean} Whether rect intersects this rectangle. + * Remove all registered events. + * @private */ -goog.math.Rect.prototype.intersects = function(rect) { - return goog.math.Rect.intersects(this, rect); +ol.pointer.PointerEventHandler.prototype.unregister_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.removeEvents_(eventSource.getEvents()); + } }; /** - * Computes the difference regions between two rectangles. The return value is - * an array of 0 to 4 rectangles defining the remaining regions of the first - * rectangle after the second has been subtracted. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {!Array} An array with 0 to 4 rectangles which - * together define the difference area of rectangle a minus rectangle b. + * Calls the right handler for a new event. + * @private + * @param {Event} inEvent Browser event. */ -goog.math.Rect.difference = function(a, b) { - var intersection = goog.math.Rect.intersection(a, b); - if (!intersection || !intersection.height || !intersection.width) { - return [a.clone()]; - } - - var result = []; - - var top = a.top; - var height = a.height; - - var ar = a.left + a.width; - var ab = a.top + a.height; - - var br = b.left + b.width; - var bb = b.top + b.height; - - // Subtract off any area on top where A extends past B - if (b.top > a.top) { - result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top)); - top = b.top; - // If we're moving the top down, we also need to subtract the height diff. - height -= b.top - a.top; - } - // Subtract off any area on bottom where A extends past B - if (bb < ab) { - result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb)); - height = bb - top; - } - // Subtract any area on left where A extends past B - if (b.left > a.left) { - result.push(new goog.math.Rect(a.left, top, b.left - a.left, height)); - } - // Subtract any area on right where A extends past B - if (br < ar) { - result.push(new goog.math.Rect(br, top, ar - br, height)); +ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) { + var type = inEvent.type; + var handler = this.eventMap_[type]; + if (handler) { + handler(inEvent); } - - return result; }; /** - * Computes the difference regions between this rectangle and {@code rect}. The - * return value is an array of 0 to 4 rectangles defining the remaining regions - * of this rectangle after the other has been subtracted. - * @param {goog.math.Rect} rect A Rectangle. - * @return {!Array} An array with 0 to 4 rectangles which - * together define the difference area of rectangle a minus rectangle b. + * Setup listeners for the given events. + * @private + * @param {Array.} events List of events. */ -goog.math.Rect.prototype.difference = function(rect) { - return goog.math.Rect.difference(this, rect); +ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) { + events.forEach(function(eventName) { + ol.events.listen(this.element_, eventName, this.eventHandler_, this); + }, this); }; /** - * Expand this rectangle to also include the area of the given rectangle. - * @param {goog.math.Rect} rect The other rectangle. + * Unregister listeners for the given events. + * @private + * @param {Array.} events List of events. */ -goog.math.Rect.prototype.boundingRect = function(rect) { - // We compute right and bottom before we change left and top below. - var right = Math.max(this.left + this.width, rect.left + rect.width); - var bottom = Math.max(this.top + this.height, rect.top + rect.height); - - this.left = Math.min(this.left, rect.left); - this.top = Math.min(this.top, rect.top); - - this.width = right - this.left; - this.height = bottom - this.top; +ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) { + events.forEach(function(e) { + ol.events.unlisten(this.element_, e, this.eventHandler_, this); + }, this); }; /** - * Returns a new rectangle which completely contains both input rectangles. - * @param {goog.math.Rect} a A rectangle. - * @param {goog.math.Rect} b A rectangle. - * @return {goog.math.Rect} A new bounding rect, or null if either rect is - * null. + * Returns a snapshot of inEvent, with writable properties. + * + * @param {Event} event Browser event. + * @param {Event|Touch} inEvent An event that contains + * properties to copy. + * @return {Object} An object containing shallow copies of + * `inEvent`'s properties. */ -goog.math.Rect.boundingRect = function(a, b) { - if (!a || !b) { - return null; +ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) { + var eventCopy = {}, p; + for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) { + p = ol.pointer.CLONE_PROPS[i][0]; + eventCopy[p] = event[p] || inEvent[p] || ol.pointer.CLONE_PROPS[i][1]; } - var clone = a.clone(); - clone.boundingRect(b); - - return clone; + return eventCopy; }; -/** - * Tests whether this rectangle entirely contains another rectangle or - * coordinate. - * - * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or - * coordinate to test for containment. - * @return {boolean} Whether this rectangle contains given rectangle or - * coordinate. - */ -goog.math.Rect.prototype.contains = function(another) { - if (another instanceof goog.math.Rect) { - return this.left <= another.left && - this.left + this.width >= another.left + another.width && - this.top <= another.top && - this.top + this.height >= another.top + another.height; - } else { // (another instanceof goog.math.Coordinate) - return another.x >= this.left && another.x <= this.left + this.width && - another.y >= this.top && another.y <= this.top + this.height; - } -}; +// EVENTS /** - * @param {!goog.math.Coordinate} point A coordinate. - * @return {number} The squared distance between the point and the closest - * point inside the rectangle. Returns 0 if the point is inside the - * rectangle. + * Triggers a 'pointerdown' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.squaredDistance = function(point) { - var dx = point.x < this.left ? - this.left - point.x : - Math.max(point.x - (this.left + this.width), 0); - var dy = point.y < this.top ? this.top - point.y : - Math.max(point.y - (this.top + this.height), 0); - return dx * dx + dy * dy; +ol.pointer.PointerEventHandler.prototype.down = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event); }; /** - * @param {!goog.math.Coordinate} point A coordinate. - * @return {number} The distance between the point and the closest point - * inside the rectangle. Returns 0 if the point is inside the rectangle. + * Triggers a 'pointermove' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.distance = function(point) { - return Math.sqrt(this.squaredDistance(point)); +ol.pointer.PointerEventHandler.prototype.move = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event); }; /** - * @return {!goog.math.Size} The size of this rectangle. + * Triggers a 'pointerup' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.getSize = function() { - return new goog.math.Size(this.width, this.height); +ol.pointer.PointerEventHandler.prototype.up = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERUP, data, event); }; /** - * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of - * the rectangle. + * Triggers a 'pointerenter' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.getTopLeft = function() { - return new goog.math.Coordinate(this.left, this.top); +ol.pointer.PointerEventHandler.prototype.enter = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event); }; /** - * @return {!goog.math.Coordinate} A new coordinate for the center of the - * rectangle. + * Triggers a 'pointerleave' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.getCenter = function() { - return new goog.math.Coordinate( - this.left + this.width / 2, this.top + this.height / 2); +ol.pointer.PointerEventHandler.prototype.leave = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event); }; /** - * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner - * of the rectangle. + * Triggers a 'pointerover' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.getBottomRight = function() { - return new goog.math.Coordinate( - this.left + this.width, this.top + this.height); +ol.pointer.PointerEventHandler.prototype.over = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event); }; /** - * Rounds the fields to the next larger integer values. - * @return {!goog.math.Rect} This rectangle with ceil'd fields. + * Triggers a 'pointerout' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.ceil = function() { - this.left = Math.ceil(this.left); - this.top = Math.ceil(this.top); - this.width = Math.ceil(this.width); - this.height = Math.ceil(this.height); - return this; +ol.pointer.PointerEventHandler.prototype.out = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event); }; /** - * Rounds the fields to the next smaller integer values. - * @return {!goog.math.Rect} This rectangle with floored fields. + * Triggers a 'pointercancel' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.floor = function() { - this.left = Math.floor(this.left); - this.top = Math.floor(this.top); - this.width = Math.floor(this.width); - this.height = Math.floor(this.height); - return this; +ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event); }; /** - * Rounds the fields to nearest integer values. - * @return {!goog.math.Rect} This rectangle with rounded fields. + * Triggers a combination of 'pointerout' and 'pointerleave' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.round = function() { - this.left = Math.round(this.left); - this.top = Math.round(this.top); - this.width = Math.round(this.width); - this.height = Math.round(this.height); - return this; +ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) { + this.out(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.leave(data, event); + } }; /** - * Translates this rectangle by the given offsets. If a - * {@code goog.math.Coordinate} is given, then the left and top values are - * translated by the coordinate's x and y values. Otherwise, top and left are - * translated by {@code tx} and {@code opt_ty} respectively. - * @param {number|goog.math.Coordinate} tx The value to translate left by or the - * the coordinate to translate this rect by. - * @param {number=} opt_ty The value to translate top by. - * @return {!goog.math.Rect} This rectangle after translating. + * Triggers a combination of 'pointerover' and 'pointerevents' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.math.Rect.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.left += tx.x; - this.top += tx.y; - } else { - this.left += goog.asserts.assertNumber(tx); - if (goog.isNumber(opt_ty)) { - this.top += opt_ty; - } +ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) { + this.over(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.enter(data, event); } - return this; }; /** - * Scales this rectangle by the given scale factors. The left and width values - * are scaled by {@code sx} and the top and height values are scaled by - * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled - * by {@code sx}. - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Rect} This rectangle after scaling. + * @private + * @param {Element} container The container element. + * @param {Element} contained The contained element. + * @return {boolean} Returns true if the container element + * contains the other element. */ -goog.math.Rect.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.left *= sx; - this.width *= sx; - this.top *= sy; - this.height *= sy; - return this; +ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) { + if (!container || !contained) { + return false; + } + return container.contains(contained); }; -// Copyright 2009 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Useful compiler idioms. - * - * @author johnlenz@google.com (John Lenz) - */ - -goog.provide('goog.reflect'); - +// EVENT CREATION AND TRACKING /** - * Syntax for object literal casts. - * @see http://go/jscompiler-renaming - * @see https://goo.gl/CRs09P - * - * Use this if you have an object literal whose keys need to have the same names - * as the properties of some class even after they are renamed by the compiler. + * Creates a new Event of type `inType`, based on the information in + * `data`. * - * @param {!Function} type Type to cast to. - * @param {Object} object Object literal to cast. - * @return {Object} The object literal. + * @param {string} inType A string representing the type of event to create. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`. */ -goog.reflect.object = function(type, object) { - return object; +ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) { + return new ol.pointer.PointerEvent(inType, event, data); }; /** - * To assert to the compiler that an operation is needed when it would - * otherwise be stripped. For example: - * - * // Force a layout - * goog.reflect.sinkValue(dialog.offsetHeight); - * - * @param {T} x - * @return {T} - * @template T + * Make and dispatch an event in one call. + * @param {string} inType A string representing the type of event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. */ -goog.reflect.sinkValue = function(x) { - goog.reflect.sinkValue[' '](x); - return x; +ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) { + var e = this.makeEvent(inType, data, event); + this.dispatchEvent(e); }; /** - * The compiler should optimize this function away iff no one ever uses - * goog.reflect.sinkValue. - */ -goog.reflect.sinkValue[' '] = goog.nullFunction; - - -/** - * Check if a property can be accessed without throwing an exception. - * @param {Object} obj The owner of the property. - * @param {string} prop The property name. - * @return {boolean} Whether the property is accessible. Will also return true - * if obj is null. + * Creates a pointer event from a native pointer event + * and dispatches this event. + * @param {Event} event A platform event with a target. */ -goog.reflect.canAccessProperty = function(obj, prop) { - /** @preserveTry */ - try { - goog.reflect.sinkValue(obj[prop]); - return true; - } catch (e) { - } - return false; +ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) { + var e = this.makeEvent(event.type, event, event); + this.dispatchEvent(e); }; /** - * Retrieves a value from a cache given a key. The compiler provides special - * consideration for this call such that it is generally considered side-effect - * free. However, if the {@code opt_keyFn} or {@code valueFn} have side-effects - * then the entire call is considered to have side-effects. - * - * Conventionally storing the value on the cache would be considered a - * side-effect and preclude unused calls from being pruned, ie. even if - * the value was never used, it would still always be stored in the cache. - * - * Providing a side-effect free {@code valueFn} and {@code opt_keyFn} - * allows unused calls to {@code goog.cache} to be pruned. - * - * @param {!Object} cacheObj The object that contains the cached values. - * @param {?} key The key to lookup in the cache. If it is not string or number - * then a {@code opt_keyFn} should be provided. The key is also used as the - * parameter to the {@code valueFn}. - * @param {!function(?):V} valueFn The value provider to use to calculate the - * value to store in the cache. This function should be side-effect free - * to take advantage of the optimization. - * @param {function(?):K=} opt_keyFn The key provider to determine the cache - * map key. This should be used if the given key is not a string or number. - * If not provided then the given key is used. This function should be - * side-effect free to take advantage of the optimization. - * @return {V} The cached or calculated value. - * @template K - * @template V + * Wrap a native mouse event into a pointer event. + * This proxy method is required for the legacy IE support. + * @param {string} eventType The pointer event type. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} The wrapped event. */ -goog.reflect.cache = function(cacheObj, key, valueFn, opt_keyFn) { - var storedKey = opt_keyFn ? opt_keyFn(key) : key; - - if (storedKey in cacheObj) { - return cacheObj[storedKey]; - } - - return (cacheObj[storedKey] = valueFn(key)); +ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) { + var pointerEvent = this.makeEvent( + eventType, ol.pointer.MouseSource.prepareEvent(event, this), event); + return pointerEvent; }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Utilities for element styles. - * - * @author arv@google.com (Erik Arvidsson) - * @author eae@google.com (Emil A Eklund) - * @see ../demos/inline_block_quirks.html - * @see ../demos/inline_block_standards.html - * @see ../demos/style_viewport.html - */ - -goog.provide('goog.style'); - - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.vendor'); -goog.require('goog.html.SafeStyleSheet'); -goog.require('goog.html.legacyconversions'); -goog.require('goog.math.Box'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Rect'); -goog.require('goog.math.Size'); -goog.require('goog.object'); -goog.require('goog.reflect'); -goog.require('goog.string'); -goog.require('goog.userAgent'); - -goog.forwardDeclare('goog.events.BrowserEvent'); -goog.forwardDeclare('goog.events.Event'); - /** - * Sets a style value on an element. - * - * This function is not indended to patch issues in the browser's style - * handling, but to allow easy programmatic access to setting dash-separated - * style properties. An example is setting a batch of properties from a data - * object without overwriting old styles. When possible, use native APIs: - * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) - * elem.style.cssText = 'property1: value1; property2: value2'. - * - * @param {Element} element The element to change. - * @param {string|Object} style If a string, a style name. If an object, a hash - * of style names to style values. - * @param {string|number|boolean=} opt_value If style was a string, then this - * should be the value. + * @inheritDoc */ -goog.style.setStyle = function(element, style, opt_value) { - if (goog.isString(style)) { - goog.style.setStyle_(element, opt_value, style); - } else { - for (var key in style) { - goog.style.setStyle_(element, style[key], key); - } - } +ol.pointer.PointerEventHandler.prototype.disposeInternal = function() { + this.unregister_(); + ol.events.EventTarget.prototype.disposeInternal.call(this); }; /** - * Sets a style value on an element, with parameters swapped to work with - * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when - * necessary. - * @param {Element} element The element to change. - * @param {string|number|boolean|undefined} value Style value. - * @param {string} style Style name. - * @private + * Constants for event names. + * @enum {string} */ -goog.style.setStyle_ = function(element, value, style) { - var propertyName = goog.style.getVendorJsStyleName_(element, style); - - if (propertyName) { - element.style[propertyName] = value; - } +ol.pointer.EventType = { + POINTERMOVE: 'pointermove', + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' }; /** - * Style name cache that stores previous property name lookups. - * - * This is used by setStyle to speed up property lookups, entries look like: - * { StyleName: ActualPropertyName } - * - * @private {!Object} + * Properties to copy when cloning an event, with default values. + * @type {Array.} */ -goog.style.styleNameCache_ = {}; - - -/** - * Returns the style property name in camel-case. If it does not exist and a - * vendor-specific version of the property does exist, then return the vendor- - * specific property name instead. - * @param {Element} element The element to change. - * @param {string} style Style name. - * @return {string} Vendor-specific style. - * @private - */ -goog.style.getVendorJsStyleName_ = function(element, style) { - var propertyName = goog.style.styleNameCache_[style]; - if (!propertyName) { - var camelStyle = goog.string.toCamelCase(style); - propertyName = camelStyle; - - if (element.style[camelStyle] === undefined) { - var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + - goog.string.toTitleCase(camelStyle); +ol.pointer.CLONE_PROPS = [ + // MouseEvent + ['bubbles', false], + ['cancelable', false], + ['view', null], + ['detail', null], + ['screenX', 0], + ['screenY', 0], + ['clientX', 0], + ['clientY', 0], + ['ctrlKey', false], + ['altKey', false], + ['shiftKey', false], + ['metaKey', false], + ['button', 0], + ['relatedTarget', null], + // DOM Level 3 + ['buttons', 0], + // PointerEvent + ['pointerId', 0], + ['width', 0], + ['height', 0], + ['pressure', 0], + ['tiltX', 0], + ['tiltY', 0], + ['pointerType', ''], + ['hwTimestamp', 0], + ['isPrimary', false], + // event instance + ['type', ''], + ['target', null], + ['currentTarget', null], + ['which', 0] +]; - if (element.style[prefixedStyle] !== undefined) { - propertyName = prefixedStyle; - } - } - goog.style.styleNameCache_[style] = propertyName; - } +goog.provide('ol.MapBrowserEvent'); +goog.provide('ol.MapBrowserEvent.EventType'); +goog.provide('ol.MapBrowserEventHandler'); +goog.provide('ol.MapBrowserPointerEvent'); - return propertyName; -}; +goog.require('goog.asserts'); +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); +goog.require('ol.pointer.PointerEvent'); +goog.require('ol.pointer.PointerEventHandler'); /** - * Returns the style property name in CSS notation. If it does not exist and a - * vendor-specific version of the property does exist, then return the vendor- - * specific property name instead. - * @param {Element} element The element to change. - * @param {string} style Style name. - * @return {string} Vendor-specific style. - * @private + * @classdesc + * Events emitted as map browser events are instances of this type. + * See {@link ol.Map} for which events trigger a map browser event. + * + * @constructor + * @extends {ol.MapEvent} + * @implements {oli.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {Event} browserEvent Browser event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. */ -goog.style.getVendorStyleName_ = function(element, style) { - var camelStyle = goog.string.toCamelCase(style); - - if (element.style[camelStyle] === undefined) { - var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + - goog.string.toTitleCase(camelStyle); +ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging, + opt_frameState) { - if (element.style[prefixedStyle] !== undefined) { - return goog.dom.vendor.getVendorPrefix() + '-' + style; - } - } + ol.MapEvent.call(this, type, map, opt_frameState); - return style; -}; + /** + * The original browser event. + * @const + * @type {Event} + * @api stable + */ + this.originalEvent = browserEvent; + /** + * The pixel of the original browser event. + * @type {ol.Pixel} + * @api stable + */ + this.pixel = map.getEventPixel(browserEvent); -/** - * Retrieves an explicitly-set style value of a node. This returns '' if there - * isn't a style attribute on the element or if this style property has not been - * explicitly set in script. - * - * @param {Element} element Element to get style of. - * @param {string} property Property to get, css-style (if you have a camel-case - * property, use element.style[style]). - * @return {string} Style value. - */ -goog.style.getStyle = function(element, property) { - // element.style is '' for well-known properties which are unset. - // For for browser specific styles as 'filter' is undefined - // so we need to return '' explicitly to make it consistent across - // browsers. - var styleValue = element.style[goog.string.toCamelCase(property)]; + /** + * The coordinate of the original browser event. + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = map.getCoordinateFromPixel(this.pixel); - // Using typeof here because of a bug in Safari 5.1, where this value - // was undefined, but === undefined returned false. - if (typeof(styleValue) !== 'undefined') { - return styleValue; - } + /** + * Indicates if the map is currently being dragged. Only set for + * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`. + * + * @type {boolean} + * @api stable + */ + this.dragging = opt_dragging !== undefined ? opt_dragging : false; - return element.style[goog.style.getVendorJsStyleName_(element, property)] || - ''; }; +ol.inherits(ol.MapBrowserEvent, ol.MapEvent); /** - * Retrieves a computed style value of a node. It returns empty string if the - * value cannot be computed (which will be the case in Internet Explorer) or - * "none" if the property requested is an SVG one and it has not been - * explicitly set (firefox and webkit). - * - * @param {Element} element Element to get style of. - * @param {string} property Property to get (camel-case). - * @return {string} Style value. + * Prevents the default browser action. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault + * @override + * @api stable */ -goog.style.getComputedStyle = function(element, property) { - var doc = goog.dom.getOwnerDocument(element); - if (doc.defaultView && doc.defaultView.getComputedStyle) { - var styles = doc.defaultView.getComputedStyle(element, null); - if (styles) { - // element.style[..] is undefined for browser specific styles - // as 'filter'. - return styles[property] || styles.getPropertyValue(property) || ''; - } - } - - return ''; +ol.MapBrowserEvent.prototype.preventDefault = function() { + ol.MapEvent.prototype.preventDefault.call(this); + this.originalEvent.preventDefault(); }; /** - * Gets the cascaded style value of a node, or null if the value cannot be - * computed (only Internet Explorer can do this). - * - * @param {Element} element Element to get style of. - * @param {string} style Property to get (camel-case). - * @return {string} Style value. + * Prevents further propagation of the current event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation + * @override + * @api stable */ -goog.style.getCascadedStyle = function(element, style) { - // TODO(nicksantos): This should be documented to return null. #fixTypes - return element.currentStyle ? element.currentStyle[style] : null; +ol.MapBrowserEvent.prototype.stopPropagation = function() { + ol.MapEvent.prototype.stopPropagation.call(this); + this.originalEvent.stopPropagation(); }; /** - * Cross-browser pseudo get computed style. It returns the computed style where - * available. If not available it tries the cascaded style value (IE - * currentStyle) and in worst case the inline style value. It shouldn't be - * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for - * discussion. - * - * @param {Element} element Element to get style of. - * @param {string} style Property to get (must be camelCase, not css-style.). - * @return {string} Style value. - * @private + * @constructor + * @extends {ol.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. */ -goog.style.getStyle_ = function(element, style) { - return goog.style.getComputedStyle(element, style) || - goog.style.getCascadedStyle(element, style) || - (element.style && element.style[style]); -}; +ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging, + opt_frameState) { + ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging, + opt_frameState); + + /** + * @const + * @type {ol.pointer.PointerEvent} + */ + this.pointerEvent = pointerEvent; -/** - * Retrieves the computed value of the box-sizing CSS attribute. - * Browser support: http://caniuse.com/css3-boxsizing. - * @param {!Element} element The element whose box-sizing to get. - * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if - * box-sizing is not supported (IE7 and below). - */ -goog.style.getComputedBoxSizing = function(element) { - return goog.style.getStyle_(element, 'boxSizing') || - goog.style.getStyle_(element, 'MozBoxSizing') || - goog.style.getStyle_(element, 'WebkitBoxSizing') || null; }; +ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent); /** - * Retrieves the computed value of the position CSS attribute. - * @param {Element} element The element to get the position of. - * @return {string} Position value. + * @param {ol.Map} map The map with the viewport to listen to events on. + * @constructor + * @extends {ol.events.EventTarget} */ -goog.style.getComputedPosition = function(element) { - return goog.style.getStyle_(element, 'position'); -}; +ol.MapBrowserEventHandler = function(map) { + ol.events.EventTarget.call(this); -/** - * Retrieves the computed background color string for a given element. The - * string returned is suitable for assigning to another element's - * background-color, but is not guaranteed to be in any particular string - * format. Accessing the color in a numeric form may not be possible in all - * browsers or with all input. - * - * If the background color for the element is defined as a hexadecimal value, - * the resulting string can be parsed by goog.color.parse in all supported - * browsers. - * - * Whether named colors like "red" or "lightblue" get translated into a - * format which can be parsed is browser dependent. Calling this function on - * transparent elements will return "transparent" in most browsers or - * "rgba(0, 0, 0, 0)" in WebKit. - * @param {Element} element The element to get the background color of. - * @return {string} The computed string value of the background color. - */ -goog.style.getBackgroundColor = function(element) { - return goog.style.getStyle_(element, 'backgroundColor'); -}; + /** + * This is the element that we will listen to the real events on. + * @type {ol.Map} + * @private + */ + this.map_ = map; + /** + * @type {number} + * @private + */ + this.clickTimeoutId_ = 0; -/** - * Retrieves the computed value of the overflow-x CSS attribute. - * @param {Element} element The element to get the overflow-x of. - * @return {string} The computed string value of the overflow-x attribute. - */ -goog.style.getComputedOverflowX = function(element) { - return goog.style.getStyle_(element, 'overflowX'); -}; + /** + * @type {boolean} + * @private + */ + this.dragging_ = false; + /** + * @type {!Array.} + * @private + */ + this.dragListenerKeys_ = []; -/** - * Retrieves the computed value of the overflow-y CSS attribute. - * @param {Element} element The element to get the overflow-y of. - * @return {string} The computed string value of the overflow-y attribute. - */ -goog.style.getComputedOverflowY = function(element) { - return goog.style.getStyle_(element, 'overflowY'); -}; + /** + * The most recent "down" type event (or null if none have occurred). + * Set on pointerdown. + * @type {ol.pointer.PointerEvent} + * @private + */ + this.down_ = null; + var element = this.map_.getViewport(); -/** - * Retrieves the computed value of the z-index CSS attribute. - * @param {Element} element The element to get the z-index of. - * @return {string|number} The computed value of the z-index attribute. - */ -goog.style.getComputedZIndex = function(element) { - return goog.style.getStyle_(element, 'zIndex'); -}; + /** + * @type {number} + * @private + */ + this.activePointers_ = 0; + /** + * @type {!Object.} + * @private + */ + this.trackedTouches_ = {}; -/** - * Retrieves the computed value of the text-align CSS attribute. - * @param {Element} element The element to get the text-align of. - * @return {string} The computed string value of the text-align attribute. - */ -goog.style.getComputedTextAlign = function(element) { - return goog.style.getStyle_(element, 'textAlign'); -}; + /** + * Event handler which generates pointer events for + * the viewport element. + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element); + /** + * Event handler which generates pointer events for + * the document (used when dragging). + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.documentPointerEventHandler_ = null; -/** - * Retrieves the computed value of the cursor CSS attribute. - * @param {Element} element The element to get the cursor of. - * @return {string} The computed string value of the cursor attribute. - */ -goog.style.getComputedCursor = function(element) { - return goog.style.getStyle_(element, 'cursor'); -}; + /** + * @type {?ol.EventsKey} + * @private + */ + this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERDOWN, + this.handlePointerDown_, this); + /** + * @type {?ol.EventsKey} + * @private + */ + this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERMOVE, + this.relayEvent_, this); -/** - * Retrieves the computed value of the CSS transform attribute. - * @param {Element} element The element to get the transform of. - * @return {string} The computed string representation of the transform matrix. - */ -goog.style.getComputedTransform = function(element) { - var property = goog.style.getVendorStyleName_(element, 'transform'); - return goog.style.getStyle_(element, property) || - goog.style.getStyle_(element, 'transform'); }; +ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget); /** - * Sets the top/left values of an element. If no unit is specified in the - * argument then it will add px. The second argument is required if the first - * argument is a string or number and is ignored if the first argument - * is a coordinate. - * @param {Element} el Element to move. - * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. - * @param {string|number=} opt_arg2 Top position. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private */ -goog.style.setPosition = function(el, arg1, opt_arg2) { - var x, y; - - if (arg1 instanceof goog.math.Coordinate) { - x = arg1.x; - y = arg1.y; +ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) { + var newEvent; + newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + if (this.clickTimeoutId_ !== 0) { + // double-click + ol.global.clearTimeout(this.clickTimeoutId_); + this.clickTimeoutId_ = 0; + newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); } else { - x = arg1; - y = opt_arg2; + // click + this.clickTimeoutId_ = ol.global.setTimeout(function() { + this.clickTimeoutId_ = 0; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + }.bind(this), 250); } - - el.style.left = goog.style.getPixelStyleValue_( - /** @type {number|string} */ (x), false); - el.style.top = goog.style.getPixelStyleValue_( - /** @type {number|string} */ (y), false); }; /** - * Gets the offsetLeft and offsetTop properties of an element and returns them - * in a Coordinate object - * @param {Element} element Element. - * @return {!goog.math.Coordinate} The position. + * Keeps track on how many pointers are currently active. + * + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private */ -goog.style.getPosition = function(element) { - return new goog.math.Coordinate( - /** @type {!HTMLElement} */ (element).offsetLeft, - /** @type {!HTMLElement} */ (element).offsetTop); +ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) { + var event = pointerEvent; + + if (event.type == ol.MapBrowserEvent.EventType.POINTERUP || + event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) { + delete this.trackedTouches_[event.pointerId]; + } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedTouches_[event.pointerId] = true; + } + this.activePointers_ = Object.keys(this.trackedTouches_).length; }; /** - * Returns the viewport element for a particular document - * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element - * of. - * @return {Element} document.documentElement or document.body. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private */ -goog.style.getClientViewportElement = function(opt_node) { - var doc; - if (opt_node) { - doc = goog.dom.getOwnerDocument(opt_node); - } else { - doc = goog.dom.getDocument(); +ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + + // We emulate click events on left mouse button click, touch contact, and pen + // contact. isMouseActionButton returns true in these cases (evt.button is set + // to 0). + // See http://www.w3.org/TR/pointerevents/#button-states + if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) { + goog.asserts.assert(this.down_, 'this.down_ must be truthy'); + this.emulateClick_(this.down_); } - // In old IE versions the document.body represented the viewport - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && - !goog.dom.getDomHelper(doc).isCss1CompatMode()) { - return doc.body; + goog.asserts.assert(this.activePointers_ >= 0, + 'this.activePointers_ should be equal to or larger than 0'); + if (this.activePointers_ === 0) { + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + this.dragging_ = false; + this.down_ = null; + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; } - return doc.documentElement; }; /** - * Calculates the viewport coordinates relative to the page/document - * containing the node. The viewport may be the browser viewport for - * non-iframe document, or the iframe container for iframe'd document. - * @param {!Document} doc The document to use as the reference point. - * @return {!goog.math.Coordinate} The page offset of the viewport. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} If the left mouse button was pressed. + * @private */ -goog.style.getViewportPageOffset = function(doc) { - var body = doc.body; - var documentElement = doc.documentElement; - var scrollLeft = body.scrollLeft || documentElement.scrollLeft; - var scrollTop = body.scrollTop || documentElement.scrollTop; - return new goog.math.Coordinate(scrollLeft, scrollTop); +ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) { + return pointerEvent.button === 0; }; /** - * Gets the client rectangle of the DOM element. - * - * getBoundingClientRect is part of a new CSS object model draft (with a - * long-time presence in IE), replacing the error-prone parent offset - * computation and the now-deprecated Gecko getBoxObjectFor. - * - * This utility patches common browser bugs in getBoundingClientRect. It - * will fail if getBoundingClientRect is unsupported. - * - * If the element is not in the DOM, the result is undefined, and an error may - * be thrown depending on user agent. - * - * @param {!Element} el The element whose bounding rectangle is being queried. - * @return {Object} A native bounding rectangle with numerical left, top, - * right, and bottom. Reported by Firefox to be of object type ClientRect. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. * @private */ -goog.style.getBoundingClientRect_ = function(el) { - var rect; - try { - rect = el.getBoundingClientRect(); - } catch (e) { - // In IE < 9, calling getBoundingClientRect on an orphan element raises an - // "Unspecified Error". All other browsers return zeros. - return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; - } - - // Patch the result in IE only, so that this function can be inlined if - // compiled for non-IE. - if (goog.userAgent.IE && el.ownerDocument.body) { - // In IE, most of the time, 2 extra pixels are added to the top and left - // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and - // IE6 standards mode, this border can be overridden by setting the - // document element's border to zero -- thus, we cannot rely on the - // offset always being 2 pixels. - - // In quirks mode, the offset can be determined by querying the body's - // clientLeft/clientTop, but in standards mode, it is found by querying - // the document element's clientLeft/clientTop. Since we already called - // getBoundingClientRect we have already forced a reflow, so it is not - // too expensive just to query them all. - - // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx - var doc = el.ownerDocument; - rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; - rect.top -= doc.documentElement.clientTop + doc.body.clientTop; - } - return rect; -}; - - -/** - * Returns the first parent that could affect the position of a given element. - * @param {Element} element The element to get the offset parent for. - * @return {Element} The first offset parent or null if one cannot be found. - */ -goog.style.getOffsetParent = function(element) { - // element.offsetParent does the right thing in IE7 and below. In other - // browsers it only includes elements with position absolute, relative or - // fixed, not elements with overflow set to auto or scroll. - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { - goog.asserts.assert(element && 'offsetParent' in element); - return element.offsetParent; - } - - var doc = goog.dom.getOwnerDocument(element); - var positionStyle = goog.style.getStyle_(element, 'position'); - var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; - for (var parent = element.parentNode; parent && parent != doc; - parent = parent.parentNode) { - // Skip shadowDOM roots. - if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT && parent.host) { - parent = parent.host; - } - positionStyle = - goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); - skipStatic = skipStatic && positionStyle == 'static' && - parent != doc.documentElement && parent != doc.body; - if (!skipStatic && - (parent.scrollWidth > parent.clientWidth || - parent.scrollHeight > parent.clientHeight || - positionStyle == 'fixed' || positionStyle == 'absolute' || - positionStyle == 'relative')) { - return /** @type {!Element} */ (parent); - } - } - return null; -}; - +ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent); + this.dispatchEvent(newEvent); -/** - * Calculates and returns the visible rectangle for a given element. Returns a - * box describing the visible portion of the nearest scrollable offset ancestor. - * Coordinates are given relative to the document. - * - * @param {Element} element Element to get the visible rect for. - * @return {goog.math.Box} Bounding elementBox describing the visible rect or - * null if scrollable ancestor isn't inside the visible viewport. - */ -goog.style.getVisibleRectForElement = function(element) { - var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); - var dom = goog.dom.getDomHelper(element); - var body = dom.getDocument().body; - var documentElement = dom.getDocument().documentElement; - var scrollEl = dom.getDocumentScrollElement(); - - // Determine the size of the visible rect by climbing the dom accounting for - // all scrollable containers. - for (var el = element; el = goog.style.getOffsetParent(el);) { - // clientWidth is zero for inline block elements in IE. - // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 - if ((!goog.userAgent.IE || el.clientWidth != 0) && - (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && - // body may have overflow set on it, yet we still get the entire - // viewport. In some browsers, el.offsetParent may be - // document.documentElement, so check for that too. - (el != body && el != documentElement && - goog.style.getStyle_(el, 'overflow') != 'visible')) { - var pos = goog.style.getPageOffset(el); - var client = goog.style.getClientLeftTop(el); - pos.x += client.x; - pos.y += client.y; - - visibleRect.top = Math.max(visibleRect.top, pos.y); - visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth); - visibleRect.bottom = - Math.min(visibleRect.bottom, pos.y + el.clientHeight); - visibleRect.left = Math.max(visibleRect.left, pos.x); - } - } - - // Clip by window's viewport. - var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; - visibleRect.left = Math.max(visibleRect.left, scrollX); - visibleRect.top = Math.max(visibleRect.top, scrollY); - var winSize = dom.getViewportSize(); - visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); - visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); - return visibleRect.top >= 0 && visibleRect.left >= 0 && - visibleRect.bottom > visibleRect.top && - visibleRect.right > visibleRect.left ? - visibleRect : - null; -}; + this.down_ = pointerEvent; + if (this.dragListenerKeys_.length === 0) { + /* Set up a pointer event handler on the `document`, + * which is required when the pointer is moved outside + * the viewport when dragging. + */ + this.documentPointerEventHandler_ = + new ol.pointer.PointerEventHandler(document); -/** - * Calculate the scroll position of {@code container} with the minimum amount so - * that the content and the borders of the given {@code element} become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * - * @param {Element} element The element to make visible. - * @param {Element=} opt_container The container to scroll. If not set, then the - * document scroll element will be used. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. - * @return {!goog.math.Coordinate} The new scroll position of the container, - * in form of goog.math.Coordinate(scrollLeft, scrollTop). - */ -goog.style.getContainerOffsetToScrollInto = function( - element, opt_container, opt_center) { - var container = opt_container || goog.dom.getDocumentScrollElement(); - // Absolute position of the element's border's top left corner. - var elementPos = goog.style.getPageOffset(element); - // Absolute position of the container's border's top left corner. - var containerPos = goog.style.getPageOffset(container); - var containerBorder = goog.style.getBorderBox(container); - if (container == goog.dom.getDocumentScrollElement()) { - // The element position is calculated based on the page offset, and the - // document scroll element holds the scroll position within the page. We can - // use the scroll position to calculate the relative position from the - // element. - var relX = elementPos.x - container.scrollLeft; - var relY = elementPos.y - container.scrollTop; - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { - // In older versions of IE getPageOffset(element) does not include the - // container border so it has to be added to accomodate. - relX += containerBorder.left; - relY += containerBorder.top; - } - } else { - // Relative pos. of the element's border box to the container's content box. - var relX = elementPos.x - containerPos.x - containerBorder.left; - var relY = elementPos.y - containerPos.y - containerBorder.top; - } - // How much the element can move in the container, i.e. the difference between - // the element's bottom-right-most and top-left-most position where it's - // fully visible. - var spaceX = container.clientWidth - - /** @type {HTMLElement} */ (element).offsetWidth; - var spaceY = container.clientHeight - - /** @type {HTMLElement} */ (element).offsetHeight; - - var scrollLeft = container.scrollLeft; - var scrollTop = container.scrollTop; - if (opt_center) { - // All browsers round non-integer scroll positions down. - scrollLeft += relX - spaceX / 2; - scrollTop += relY - spaceY / 2; - } else { - // This formula was designed to give the correct scroll values in the - // following cases: - // - element is higher than container (spaceY < 0) => scroll down by relY - // - element is not higher that container (spaceY >= 0): - // - it is above container (relY < 0) => scroll up by abs(relY) - // - it is below container (relY > spaceY) => scroll down by relY - spaceY - // - it is in the container => don't scroll - scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); - scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); + this.dragListenerKeys_.push( + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERMOVE, + this.handlePointerMove_, this), + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERUP, + this.handlePointerUp_, this), + /* Note that the listener for `pointercancel is set up on + * `pointerEventHandler_` and not `documentPointerEventHandler_` like + * the `pointerup` and `pointermove` listeners. + * + * The reason for this is the following: `TouchSource.vacuumTouches_()` + * issues `pointercancel` events, when there was no `touchend` for a + * `touchstart`. Now, let's say a first `touchstart` is registered on + * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up. + * But `documentPointerEventHandler_` doesn't know about the first + * `touchstart`. If there is no `touchend` for the `touchstart`, we can + * only receive a `touchcancel` from `pointerEventHandler_`, because it is + * only registered there. + */ + ol.events.listen(this.pointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERCANCEL, + this.handlePointerUp_, this) + ); } - return new goog.math.Coordinate(scrollLeft, scrollTop); -}; - - -/** - * Changes the scroll position of {@code container} with the minimum amount so - * that the content and the borders of the given {@code element} become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * - * @param {Element} element The element to make visible. - * @param {Element=} opt_container The container to scroll. If not set, then the - * document scroll element will be used. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. - */ -goog.style.scrollIntoContainerView = function( - element, opt_container, opt_center) { - var container = opt_container || goog.dom.getDocumentScrollElement(); - var offset = - goog.style.getContainerOffsetToScrollInto(element, container, opt_center); - container.scrollLeft = offset.x; - container.scrollTop = offset.y; -}; - - -/** - * Returns clientLeft (width of the left border and, if the directionality is - * right to left, the vertical scrollbar) and clientTop as a coordinate object. - * - * @param {Element} el Element to get clientLeft for. - * @return {!goog.math.Coordinate} Client left and top. - */ -goog.style.getClientLeftTop = function(el) { - return new goog.math.Coordinate(el.clientLeft, el.clientTop); }; /** - * Returns a Coordinate object relative to the top-left of the HTML document. - * Implemented as a single function to save having to do two recursive loops in - * opera and safari just to get both coordinates. If you just want one value do - * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but - * note if you call both those methods the tree will be analysed twice. - * - * @param {Element} el Element to get the page offset for. - * @return {!goog.math.Coordinate} The page offset. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private */ -goog.style.getPageOffset = function(el) { - var doc = goog.dom.getOwnerDocument(el); - // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. - goog.asserts.assertObject(el, 'Parameter is required'); - - // NOTE(arv): If element is hidden (display none or disconnected or any the - // ancestors are hidden) we get (0,0) by default but we still do the - // accumulation of scroll position. - - // TODO(arv): Should we check if the node is disconnected and in that case - // return (0,0)? - - var pos = new goog.math.Coordinate(0, 0); - var viewportElement = goog.style.getClientViewportElement(doc); - if (el == viewportElement) { - // viewport is always at 0,0 as that defined the coordinate system for this - // function - this avoids special case checks in the code below - return pos; +ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) { + // Fix IE10 on windows Surface : When you tap the tablet, it triggers + // multiple pointermove events between pointerdown and pointerup with + // the exact same coordinates of the pointerdown event. To avoid a + // 'false' touchmove event to be dispatched , we test if the pointer + // effectively moved. + if (this.isMoving_(pointerEvent)) { + this.dragging_ = true; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent, + this.dragging_); + this.dispatchEvent(newEvent); } - var box = goog.style.getBoundingClientRect_(el); - // Must add the scroll coordinates in to get the absolute page offset - // of element since getBoundingClientRect returns relative coordinates to - // the viewport. - var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); - pos.x = box.left + scrollCoord.x; - pos.y = box.top + scrollCoord.y; - - return pos; + // Some native android browser triggers mousemove events during small period + // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or + // https://code.google.com/p/android/issues/detail?id=19827 + // ex: Galaxy Tab P3110 + Android 4.1.1 + pointerEvent.preventDefault(); }; /** - * Returns the left coordinate of an element relative to the HTML document - * @param {Element} el Elements. - * @return {number} The left coordinate. + * Wrap and relay a pointer event. Note that this requires that the type + * string for the MapBrowserPointerEvent matches the PointerEvent type. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private */ -goog.style.getPageOffsetLeft = function(el) { - return goog.style.getPageOffset(el).x; +ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) { + var dragging = !!(this.down_ && this.isMoving_(pointerEvent)); + this.dispatchEvent(new ol.MapBrowserPointerEvent( + pointerEvent.type, this.map_, pointerEvent, dragging)); }; /** - * Returns the top coordinate of an element relative to the HTML document - * @param {Element} el Elements. - * @return {number} The top coordinate. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} Is moving. + * @private */ -goog.style.getPageOffsetTop = function(el) { - return goog.style.getPageOffset(el).y; +ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) { + return pointerEvent.clientX != this.down_.clientX || + pointerEvent.clientY != this.down_.clientY; }; /** - * Returns a Coordinate object relative to the top-left of an HTML document - * in an ancestor frame of this element. Used for measuring the position of - * an element inside a frame relative to a containing frame. - * - * @param {Element} el Element to get the page offset for. - * @param {Window} relativeWin The window to measure relative to. If relativeWin - * is not in the ancestor frame chain of the element, we measure relative to - * the top-most window. - * @return {!goog.math.Coordinate} The page offset. + * @inheritDoc */ -goog.style.getFramedPageOffset = function(el, relativeWin) { - var position = new goog.math.Coordinate(0, 0); - - // Iterate up the ancestor frame chain, keeping track of the current window - // and the current element in that window. - var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); - - // MS Edge throws when accessing "parent" if el's containing iframe has been - // deleted. - if (!goog.reflect.canAccessProperty(currentWin, 'parent')) { - return position; +ol.MapBrowserEventHandler.prototype.disposeInternal = function() { + if (this.relayedListenerKey_) { + ol.events.unlistenByKey(this.relayedListenerKey_); + this.relayedListenerKey_ = null; + } + if (this.pointerdownListenerKey_) { + ol.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; } - var currentEl = el; - do { - // if we're at the top window, we want to get the page offset. - // if we're at an inner frame, we only want to get the window position - // so that we can determine the actual page offset in the context of - // the outer window. - var offset = currentWin == relativeWin ? - goog.style.getPageOffset(currentEl) : - goog.style.getClientPositionForElement_(goog.asserts.assert(currentEl)); - - position.x += offset.x; - position.y += offset.y; - } while (currentWin && currentWin != relativeWin && - currentWin != currentWin.parent && - (currentEl = currentWin.frameElement) && - (currentWin = currentWin.parent)); + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; - return position; + if (this.documentPointerEventHandler_) { + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; + } + if (this.pointerEventHandler_) { + this.pointerEventHandler_.dispose(); + this.pointerEventHandler_ = null; + } + ol.events.EventTarget.prototype.disposeInternal.call(this); }; /** - * Translates the specified rect relative to origBase page, for newBase page. - * If origBase and newBase are the same, this function does nothing. - * - * @param {goog.math.Rect} rect The source rectangle relative to origBase page, - * and it will have the translated result. - * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. - * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant - * coordinate. This must be a DOM for an ancestor frame of origBase - * or the same as origBase. + * Constants for event names. + * @enum {string} */ -goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { - if (origBase.getDocument() != newBase.getDocument()) { - var body = origBase.getDocument().body; - var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); +ol.MapBrowserEvent.EventType = { + + /** + * A true single click with no dragging and no double click. Note that this + * event is delayed by 250 ms to ensure that it is not a double click. + * @event ol.MapBrowserEvent#singleclick + * @api stable + */ + SINGLECLICK: 'singleclick', - // Adjust Body's margin. - pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); + /** + * A click with no dragging. A double click will fire two of this. + * @event ol.MapBrowserEvent#click + * @api stable + */ + CLICK: ol.events.EventType.CLICK, - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && - !origBase.isCss1CompatMode()) { - pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); - } + /** + * A true double click, with no dragging. + * @event ol.MapBrowserEvent#dblclick + * @api stable + */ + DBLCLICK: ol.events.EventType.DBLCLICK, - rect.left += pos.x; - rect.top += pos.y; - } -}; + /** + * Triggered when a pointer is dragged. + * @event ol.MapBrowserEvent#pointerdrag + * @api + */ + POINTERDRAG: 'pointerdrag', + /** + * Triggered when a pointer is moved. Note that on touch devices this is + * triggered when the map is panned, so is not the same as mousemove. + * @event ol.MapBrowserEvent#pointermove + * @api stable + */ + POINTERMOVE: 'pointermove', -/** - * Returns the position of an element relative to another element in the - * document. A relative to B - * @param {Element|Event|goog.events.Event} a Element or mouse event whose - * position we're calculating. - * @param {Element|Event|goog.events.Event} b Element or mouse event position - * is relative to. - * @return {!goog.math.Coordinate} The relative position. - */ -goog.style.getRelativePosition = function(a, b) { - var ap = goog.style.getClientPosition(a); - var bp = goog.style.getClientPosition(b); - return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' }; +goog.provide('ol.layer.Base'); +goog.provide('ol.layer.LayerProperty'); -/** - * Returns the position of the event or the element's border box relative to - * the client viewport. - * @param {!Element} el Element whose position to get. - * @return {!goog.math.Coordinate} The position. - * @private - */ -goog.style.getClientPositionForElement_ = function(el) { - var box = goog.style.getBoundingClientRect_(el); - return new goog.math.Coordinate(box.left, box.top); -}; +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.math'); +goog.require('ol.object'); +goog.require('ol.source.State'); /** - * Returns the position of the event or the element's border box relative to - * the client viewport. If an event is passed, and if this event is a "touch" - * event, then the position of the first changedTouches will be returned. - * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. - * @return {!goog.math.Coordinate} The position. + * @enum {string} */ -goog.style.getClientPosition = function(el) { - goog.asserts.assert(el); - if (el.nodeType == goog.dom.NodeType.ELEMENT) { - return goog.style.getClientPositionForElement_( - /** @type {!Element} */ (el)); - } else { - var targetEvent = el.changedTouches ? el.changedTouches[0] : el; - return new goog.math.Coordinate(targetEvent.clientX, targetEvent.clientY); - } +ol.layer.LayerProperty = { + OPACITY: 'opacity', + VISIBLE: 'visible', + EXTENT: 'extent', + Z_INDEX: 'zIndex', + MAX_RESOLUTION: 'maxResolution', + MIN_RESOLUTION: 'minResolution', + SOURCE: 'source' }; /** - * Moves an element to the given coordinates relative to the client viewport. - * @param {Element} el Absolutely positioned element to set page offset for. - * It must be in the document. - * @param {number|goog.math.Coordinate} x Left position of the element's margin - * box or a coordinate object. - * @param {number=} opt_y Top position of the element's margin box. - */ -goog.style.setPageOffset = function(el, x, opt_y) { - // Get current pageoffset - var cur = goog.style.getPageOffset(el); - - if (x instanceof goog.math.Coordinate) { - opt_y = x.y; - x = x.x; - } + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Note that with `ol.layer.Base` and all its subclasses, any property set in + * the options is set as a {@link ol.Object} property on the layer object, so + * is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.Object} + * @param {olx.layer.BaseOptions} options Layer options. + * @api stable + */ +ol.layer.Base = function(options) { - // NOTE(arv): We cannot allow strings for x and y. We could but that would - // require us to manually transform between different units + ol.Object.call(this); - // Work out deltas - var dx = goog.asserts.assertNumber(x) - cur.x; - var dy = Number(opt_y) - cur.y; + /** + * @type {Object.} + */ + var properties = ol.object.assign({}, options); + properties[ol.layer.LayerProperty.OPACITY] = + options.opacity !== undefined ? options.opacity : 1; + properties[ol.layer.LayerProperty.VISIBLE] = + options.visible !== undefined ? options.visible : true; + properties[ol.layer.LayerProperty.Z_INDEX] = + options.zIndex !== undefined ? options.zIndex : 0; + properties[ol.layer.LayerProperty.MAX_RESOLUTION] = + options.maxResolution !== undefined ? options.maxResolution : Infinity; + properties[ol.layer.LayerProperty.MIN_RESOLUTION] = + options.minResolution !== undefined ? options.minResolution : 0; - // Set position to current left/top + delta - goog.style.setPosition( - el, /** @type {!HTMLElement} */ (el).offsetLeft + dx, - /** @type {!HTMLElement} */ (el).offsetTop + dy); + this.setProperties(properties); }; +ol.inherits(ol.layer.Base, ol.Object); /** - * Sets the width/height values of an element. If an argument is numeric, - * or a goog.math.Size is passed, it is assumed to be pixels and will add - * 'px' after converting it to an integer in string form. (This just sets the - * CSS width and height properties so it might set content-box or border-box - * size depending on the box model the browser is using.) - * - * @param {Element} element Element to set the size of. - * @param {string|number|goog.math.Size} w Width of the element, or a - * size object. - * @param {string|number=} opt_h Height of the element. Required if w is not a - * size object. + * @return {ol.LayerState} Layer state. */ -goog.style.setSize = function(element, w, opt_h) { - var h; - if (w instanceof goog.math.Size) { - h = w.height; - w = w.width; - } else { - if (opt_h == undefined) { - throw Error('missing height argument'); - } - h = opt_h; - } - - goog.style.setWidth(element, /** @type {string|number} */ (w)); - goog.style.setHeight(element, h); +ol.layer.Base.prototype.getLayerState = function() { + var opacity = this.getOpacity(); + var sourceState = this.getSourceState(); + var visible = this.getVisible(); + var extent = this.getExtent(); + var zIndex = this.getZIndex(); + var maxResolution = this.getMaxResolution(); + var minResolution = this.getMinResolution(); + return { + layer: /** @type {ol.layer.Layer} */ (this), + opacity: ol.math.clamp(opacity, 0, 1), + sourceState: sourceState, + visible: visible, + managed: true, + extent: extent, + zIndex: zIndex, + maxResolution: maxResolution, + minResolution: Math.max(minResolution, 0) + }; }; /** - * Helper function to create a string to be set into a pixel-value style - * property of an element. Can round to the nearest integer value. - * - * @param {string|number} value The style value to be used. If a number, - * 'px' will be appended, otherwise the value will be applied directly. - * @param {boolean} round Whether to round the nearest integer (if property - * is a number). - * @return {string} The string value for the property. - * @private + * @param {Array.=} opt_array Array of layers (to be + * modified in place). + * @return {Array.} Array of layers. */ -goog.style.getPixelStyleValue_ = function(value, round) { - if (typeof value == 'number') { - value = (round ? Math.round(value) : value) + 'px'; - } - - return value; -}; +ol.layer.Base.prototype.getLayersArray = goog.abstractMethod; /** - * Set the height of an element. Sets the element's style property. - * @param {Element} element Element to set the height of. - * @param {string|number} height The height value to set. If a number, 'px' - * will be appended, otherwise the value will be applied directly. + * @param {Array.=} opt_states Optional list of layer + * states (to be modified in place). + * @return {Array.} List of layer states. */ -goog.style.setHeight = function(element, height) { - element.style.height = goog.style.getPixelStyleValue_(height, true); -}; +ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod; /** - * Set the width of an element. Sets the element's style property. - * @param {Element} element Element to set the width of. - * @param {string|number} width The width value to set. If a number, 'px' - * will be appended, otherwise the value will be applied directly. + * Return the {@link ol.Extent extent} of the layer or `undefined` if it + * will be visible regardless of extent. + * @return {ol.Extent|undefined} The layer extent. + * @observable + * @api stable */ -goog.style.setWidth = function(element, width) { - element.style.width = goog.style.getPixelStyleValue_(width, true); +ol.layer.Base.prototype.getExtent = function() { + return /** @type {ol.Extent|undefined} */ ( + this.get(ol.layer.LayerProperty.EXTENT)); }; /** - * Gets the height and width of an element, even if its display is none. - * - * Specifically, this returns the height and width of the border box, - * irrespective of the box model in effect. - * - * Note that this function does not take CSS transforms into account. Please see - * {@code goog.style.getTransformedSize}. - * @param {Element} element Element to get size of. - * @return {!goog.math.Size} Object with width/height properties. + * Return the maximum resolution of the layer. + * @return {number} The maximum resolution of the layer. + * @observable + * @api stable */ -goog.style.getSize = function(element) { - return goog.style.evaluateWithTemporaryDisplay_( - goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); +ol.layer.Base.prototype.getMaxResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.LayerProperty.MAX_RESOLUTION)); }; /** - * Call {@code fn} on {@code element} such that {@code element}'s dimensions are - * accurate when it's passed to {@code fn}. - * @param {function(!Element): T} fn Function to call with {@code element} as - * an argument after temporarily changing {@code element}'s display such - * that its dimensions are accurate. - * @param {!Element} element Element (which may have display none) to use as - * argument to {@code fn}. - * @return {T} Value returned by calling {@code fn} with {@code element}. - * @template T - * @private + * Return the minimum resolution of the layer. + * @return {number} The minimum resolution of the layer. + * @observable + * @api stable */ -goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { - if (goog.style.getStyle_(element, 'display') != 'none') { - return fn(element); - } - - var style = element.style; - var originalDisplay = style.display; - var originalVisibility = style.visibility; - var originalPosition = style.position; - - style.visibility = 'hidden'; - style.position = 'absolute'; - style.display = 'inline'; - - var retVal = fn(element); - - style.display = originalDisplay; - style.position = originalPosition; - style.visibility = originalVisibility; - - return retVal; +ol.layer.Base.prototype.getMinResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.LayerProperty.MIN_RESOLUTION)); }; /** - * Gets the height and width of an element when the display is not none. - * @param {Element} element Element to get size of. - * @return {!goog.math.Size} Object with width/height properties. - * @private + * Return the opacity of the layer (between 0 and 1). + * @return {number} The opacity of the layer. + * @observable + * @api stable */ -goog.style.getSizeWithDisplay_ = function(element) { - var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth; - var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight; - var webkitOffsetsZero = - goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; - if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && - element.getBoundingClientRect) { - // Fall back to calling getBoundingClientRect when offsetWidth or - // offsetHeight are not defined, or when they are zero in WebKit browsers. - // This makes sure that we return for the correct size for SVG elements, but - // will still return 0 on Webkit prior to 534.8, see - // http://trac.webkit.org/changeset/67252. - var clientRect = goog.style.getBoundingClientRect_(element); - return new goog.math.Size( - clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); - } - return new goog.math.Size(offsetWidth, offsetHeight); +ol.layer.Base.prototype.getOpacity = function() { + return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY)); }; /** - * Gets the height and width of an element, post transform, even if its display - * is none. - * - * This is like {@code goog.style.getSize}, except: - *
    - *
  1. Takes webkitTransforms such as rotate and scale into account. - *
  2. Will return null if {@code element} doesn't respond to - * {@code getBoundingClientRect}. - *
  3. Currently doesn't make sense on non-WebKit browsers which don't support - * webkitTransforms. - *
- * @param {!Element} element Element to get size of. - * @return {goog.math.Size} Object with width/height properties. + * @return {ol.source.State} Source state. */ -goog.style.getTransformedSize = function(element) { - if (!element.getBoundingClientRect) { - return null; - } - - var clientRect = goog.style.evaluateWithTemporaryDisplay_( - goog.style.getBoundingClientRect_, element); - return new goog.math.Size( - clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); -}; +ol.layer.Base.prototype.getSourceState = goog.abstractMethod; /** - * Returns a bounding rectangle for a given element in page space. - * @param {Element} element Element to get bounds of. Must not be display none. - * @return {!goog.math.Rect} Bounding rectangle for the element. + * Return the visibility of the layer (`true` or `false`). + * @return {boolean} The visibility of the layer. + * @observable + * @api stable */ -goog.style.getBounds = function(element) { - var o = goog.style.getPageOffset(element); - var s = goog.style.getSize(element); - return new goog.math.Rect(o.x, o.y, s.width, s.height); +ol.layer.Base.prototype.getVisible = function() { + return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE)); }; /** - * Converts a CSS selector in the form style-property to styleProperty. - * @param {*} selector CSS Selector. - * @return {string} Camel case selector. - * @deprecated Use goog.string.toCamelCase instead. + * Return the Z-index of the layer, which is used to order layers before + * rendering. The default Z-index is 0. + * @return {number} The Z-index of the layer. + * @observable + * @api */ -goog.style.toCamelCase = function(selector) { - return goog.string.toCamelCase(String(selector)); +ol.layer.Base.prototype.getZIndex = function() { + return /** @type {number} */ (this.get(ol.layer.LayerProperty.Z_INDEX)); }; /** - * Converts a CSS selector in the form styleProperty to style-property. - * @param {string} selector Camel case selector. - * @return {string} Selector cased. - * @deprecated Use goog.string.toSelectorCase instead. + * Set the extent at which the layer is visible. If `undefined`, the layer + * will be visible at all extents. + * @param {ol.Extent|undefined} extent The extent of the layer. + * @observable + * @api stable */ -goog.style.toSelectorCase = function(selector) { - return goog.string.toSelectorCase(selector); +ol.layer.Base.prototype.setExtent = function(extent) { + this.set(ol.layer.LayerProperty.EXTENT, extent); }; /** - * Gets the opacity of a node (x-browser). This gets the inline style opacity - * of the node, and does not take into account the cascaded or the computed - * style for this node. - * @param {Element} el Element whose opacity has to be found. - * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} - * if the opacity is not set. + * Set the maximum resolution at which the layer is visible. + * @param {number} maxResolution The maximum resolution of the layer. + * @observable + * @api stable */ -goog.style.getOpacity = function(el) { - goog.asserts.assert(el); - var style = el.style; - var result = ''; - if ('opacity' in style) { - result = style.opacity; - } else if ('MozOpacity' in style) { - result = style.MozOpacity; - } else if ('filter' in style) { - var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); - if (match) { - result = String(match[1] / 100); - } - } - return result == '' ? result : Number(result); +ol.layer.Base.prototype.setMaxResolution = function(maxResolution) { + this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution); }; /** - * Sets the opacity of a node (x-browser). - * @param {Element} el Elements whose opacity has to be set. - * @param {number|string} alpha Opacity between 0 and 1 or an empty string - * {@code ''} to clear the opacity. + * Set the minimum resolution at which the layer is visible. + * @param {number} minResolution The minimum resolution of the layer. + * @observable + * @api stable */ -goog.style.setOpacity = function(el, alpha) { - goog.asserts.assert(el); - var style = el.style; - if ('opacity' in style) { - style.opacity = alpha; - } else if ('MozOpacity' in style) { - style.MozOpacity = alpha; - } else if ('filter' in style) { - // TODO(arv): Overwriting the filter might have undesired side effects. - if (alpha === '') { - style.filter = ''; - } else { - style.filter = 'alpha(opacity=' + (Number(alpha) * 100) + ')'; - } - } +ol.layer.Base.prototype.setMinResolution = function(minResolution) { + this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution); }; /** - * Sets the background of an element to a transparent image in a browser- - * independent manner. - * - * This function does not support repeating backgrounds or alternate background - * positions to match the behavior of Internet Explorer. It also does not - * support sizingMethods other than crop since they cannot be replicated in - * browsers other than Internet Explorer. - * - * @param {Element} el The element to set background on. - * @param {string} src The image source URL. + * Set the opacity of the layer, allowed values range from 0 to 1. + * @param {number} opacity The opacity of the layer. + * @observable + * @api stable */ -goog.style.setTransparentBackgroundImage = function(el, src) { - var style = el.style; - // It is safe to use the style.filter in IE only. In Safari 'filter' is in - // style object but access to style.filter causes it to throw an exception. - // Note: IE8 supports images with an alpha channel. - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - // See TODO in setOpacity. - style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + - 'src="' + src + '", sizingMethod="crop")'; - } else { - // Set style properties individually instead of using background shorthand - // to prevent overwriting a pre-existing background color. - style.backgroundImage = 'url(' + src + ')'; - style.backgroundPosition = 'top left'; - style.backgroundRepeat = 'no-repeat'; - } +ol.layer.Base.prototype.setOpacity = function(opacity) { + this.set(ol.layer.LayerProperty.OPACITY, opacity); }; /** - * Clears the background image of an element in a browser independent manner. - * @param {Element} el The element to clear background image for. + * Set the visibility of the layer (`true` or `false`). + * @param {boolean} visible The visibility of the layer. + * @observable + * @api stable */ -goog.style.clearTransparentBackgroundImage = function(el) { - var style = el.style; - if ('filter' in style) { - // See TODO in setOpacity. - style.filter = ''; - } else { - // Set style properties individually instead of using background shorthand - // to prevent overwriting a pre-existing background color. - style.backgroundImage = 'none'; - } +ol.layer.Base.prototype.setVisible = function(visible) { + this.set(ol.layer.LayerProperty.VISIBLE, visible); }; /** - * Shows or hides an element from the page. Hiding the element is done by - * setting the display property to "none", removing the element from the - * rendering hierarchy so it takes up no space. To show the element, the default - * inherited display property is restored (defined either in stylesheets or by - * the browser's default style rules.) - * - * Caveat 1: if the inherited display property for the element is set to "none" - * by the stylesheets, that is the property that will be restored by a call to - * showElement(), effectively toggling the display between "none" and "none". - * - * Caveat 2: if the element display style is set inline (by setting either - * element.style.display or a style attribute in the HTML), a call to - * showElement will clear that setting and defer to the inherited style in the - * stylesheet. - * @param {Element} el Element to show or hide. - * @param {*} display True to render the element in its default style, - * false to disable rendering the element. - * @deprecated Use goog.style.setElementShown instead. + * Set Z-index of the layer, which is used to order layers before rendering. + * The default Z-index is 0. + * @param {number} zindex The z-index of the layer. + * @observable + * @api */ -goog.style.showElement = function(el, display) { - goog.style.setElementShown(el, display); +ol.layer.Base.prototype.setZIndex = function(zindex) { + this.set(ol.layer.LayerProperty.Z_INDEX, zindex); }; +goog.provide('ol.render.VectorContext'); + /** - * Shows or hides an element from the page. Hiding the element is done by - * setting the display property to "none", removing the element from the - * rendering hierarchy so it takes up no space. To show the element, the default - * inherited display property is restored (defined either in stylesheets or by - * the browser's default style rules). - * - * Caveat 1: if the inherited display property for the element is set to "none" - * by the stylesheets, that is the property that will be restored by a call to - * setElementShown(), effectively toggling the display between "none" and - * "none". - * - * Caveat 2: if the element display style is set inline (by setting either - * element.style.display or a style attribute in the HTML), a call to - * setElementShown will clear that setting and defer to the inherited style in - * the stylesheet. - * @param {Element} el Element to show or hide. - * @param {*} isShown True to render the element in its default style, - * false to disable rendering the element. + * Context for drawing geometries. A vector context is available on render + * events and does not need to be constructed directly. + * @constructor + * @struct + * @api */ -goog.style.setElementShown = function(el, isShown) { - el.style.display = isShown ? '' : 'none'; +ol.render.VectorContext = function() { }; /** - * Test whether the given element has been shown or hidden via a call to - * {@link #setElementShown}. - * - * Note this is strictly a companion method for a call - * to {@link #setElementShown} and the same caveats apply; in particular, this - * method does not guarantee that the return value will be consistent with - * whether or not the element is actually visible. + * Render a geometry. * - * @param {Element} el The element to test. - * @return {boolean} Whether the element has been shown. - * @see #setElementShown + * @param {ol.geom.Geometry} geometry The geometry to render. */ -goog.style.isElementShown = function(el) { - return el.style.display != 'none'; -}; +ol.render.VectorContext.prototype.drawGeometry = goog.abstractMethod; /** - * Installs the styles string into the window that contains opt_node. If - * opt_node is null, the main window is used. - * @param {string} stylesString The style string to install. - * @param {Node=} opt_node Node whose parent document should have the - * styles installed. - * @return {!Element|!StyleSheet} The style element created. - * @deprecated Use {@link #installSafeStyleSheet} instead. + * Set the rendering style. + * + * @param {ol.style.Style} style The rendering style. */ -goog.style.installStyles = function(stylesString, opt_node) { - return goog.style.installSafeStyleSheet( - goog.html.legacyconversions.safeStyleSheetFromString(stylesString), - opt_node); -}; +ol.render.VectorContext.prototype.setStyle = goog.abstractMethod; /** - * Installs the style sheet into the window that contains opt_node. If - * opt_node is null, the main window is used. - * @param {!goog.html.SafeStyleSheet} safeStyleSheet The style sheet to install. - * @param {?Node=} opt_node Node whose parent document should have the - * styles installed. - * @return {!Element|!StyleSheet} The style element created. + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {ol.Feature} feature Feature, */ -goog.style.installSafeStyleSheet = function(safeStyleSheet, opt_node) { - var dh = goog.dom.getDomHelper(opt_node); - var styleSheet = null; - - // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be - // undefined as of IE 11. - var doc = dh.getDocument(); - if (goog.userAgent.IE && doc.createStyleSheet) { - styleSheet = doc.createStyleSheet(); - goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet); - } else { - var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0]; - - // In opera documents are not guaranteed to have a head element, thus we - // have to make sure one exists before using it. - if (!head) { - var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0]; - head = dh.createDom(goog.dom.TagName.HEAD); - body.parentNode.insertBefore(head, body); - } - styleSheet = dh.createDom(goog.dom.TagName.STYLE); - // NOTE(user): Setting styles after the style element has been appended - // to the head results in a nasty Webkit bug in certain scenarios. Please - // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional - // details. - goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet); - dh.appendChild(head, styleSheet); - } - return styleSheet; -}; +ol.render.VectorContext.prototype.drawCircle = goog.abstractMethod; /** - * Removes the styles added by {@link #installStyles}. - * @param {Element|StyleSheet} styleSheet The value returned by - * {@link #installStyles}. + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. */ -goog.style.uninstallStyles = function(styleSheet) { - var node = styleSheet.ownerNode || styleSheet.owningElement || - /** @type {Element} */ (styleSheet); - goog.dom.removeNode(node); -}; +ol.render.VectorContext.prototype.drawFeature = goog.abstractMethod; /** - * Sets the content of a style element. The style element can be any valid - * style element. This element will have its content completely replaced by - * the stylesString. - * @param {Element|StyleSheet} element A stylesheet element as returned by - * installStyles. - * @param {string} stylesString The new content of the stylesheet. - * @deprecated Use {@link #setSafeStyleSheet} instead. + * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry + * collection. + * @param {ol.Feature} feature Feature. */ -goog.style.setStyles = function(element, stylesString) { - goog.style.setSafeStyleSheet(/** @type {!Element|!StyleSheet} */ (element), - goog.html.legacyconversions.safeStyleSheetFromString(stylesString)); -}; +ol.render.VectorContext.prototype.drawGeometryCollection = goog.abstractMethod; /** - * Sets the content of a style element. The style element can be any valid - * style element. This element will have its content completely replaced by - * the safeStyleSheet. - * @param {!Element|!StyleSheet} element A stylesheet element as returned by - * installStyles. - * @param {!goog.html.SafeStyleSheet} safeStyleSheet The new content of the - * stylesheet. + * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line + * string geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.setSafeStyleSheet = function(element, safeStyleSheet) { - var stylesString = goog.html.SafeStyleSheet.unwrap(safeStyleSheet); - if (goog.userAgent.IE && goog.isDef(element.cssText)) { - // Adding the selectors individually caused the browser to hang if the - // selector was invalid or there were CSS comments. Setting the cssText of - // the style node works fine and ignores CSS that IE doesn't understand. - // However IE >= 11 doesn't support cssText any more, so we make sure that - // cssText is a defined property and otherwise fall back to setTextContent. - element.cssText = stylesString; - } else { - // NOTE: We could also set textContent directly here. - goog.dom.setTextContent(/** @type {!Element} */ (element), stylesString); - } -}; +ol.render.VectorContext.prototype.drawLineString = goog.abstractMethod; /** - * Sets 'white-space: pre-wrap' for a node (x-browser). - * - * There are as many ways of specifying pre-wrap as there are browsers. - * - * CSS3/IE8: white-space: pre-wrap; - * Mozilla: white-space: -moz-pre-wrap; - * Opera: white-space: -o-pre-wrap; - * IE6/7: white-space: pre; word-wrap: break-word; - * - * @param {Element} el Element to enable pre-wrap for. + * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry + * MultiLineString geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.setPreWrap = function(el) { - var style = el.style; - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - style.whiteSpace = 'pre'; - style.wordWrap = 'break-word'; - } else if (goog.userAgent.GECKO) { - style.whiteSpace = '-moz-pre-wrap'; - } else { - style.whiteSpace = 'pre-wrap'; - } -}; +ol.render.VectorContext.prototype.drawMultiLineString = goog.abstractMethod; /** - * Sets 'display: inline-block' for an element (cross-browser). - * @param {Element} el Element to which the inline-block display style is to be - * applied. - * @see ../demos/inline_block_quirks.html - * @see ../demos/inline_block_standards.html + * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.setInlineBlock = function(el) { - var style = el.style; - // Without position:relative, weirdness ensues. Just accept it and move on. - style.position = 'relative'; - - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - // IE8 supports inline-block so fall through to the else - // Zoom:1 forces hasLayout, display:inline gives inline behavior. - style.zoom = '1'; - style.display = 'inline'; - } else { - // Opera, Webkit, and Safari seem to do OK with the standard inline-block - // style. - style.display = 'inline-block'; - } -}; +ol.render.VectorContext.prototype.drawMultiPoint = goog.abstractMethod; /** - * Returns true if the element is using right to left (rtl) direction. - * @param {Element} el The element to test. - * @return {boolean} True for right to left, false for left to right. + * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. + * @param {ol.Feature} feature Feature. */ -goog.style.isRightToLeft = function(el) { - return 'rtl' == goog.style.getStyle_(el, 'direction'); -}; +ol.render.VectorContext.prototype.drawMultiPolygon = goog.abstractMethod; /** - * The CSS style property corresponding to an element being - * unselectable on the current browser platform (null if none). - * Opera and IE instead use a DOM attribute 'unselectable'. MS Edge uses - * the Webkit prefix. - * @type {?string} - * @private + * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.unselectableStyle_ = goog.userAgent.GECKO ? - 'MozUserSelect' : - goog.userAgent.WEBKIT || goog.userAgent.EDGE ? 'WebkitUserSelect' : null; +ol.render.VectorContext.prototype.drawPoint = goog.abstractMethod; /** - * Returns true if the element is set to be unselectable, false otherwise. - * Note that on some platforms (e.g. Mozilla), even if an element isn't set - * to be unselectable, it will behave as such if any of its ancestors is - * unselectable. - * @param {Element} el Element to check. - * @return {boolean} Whether the element is set to be unselectable. + * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.isUnselectable = function(el) { - if (goog.style.unselectableStyle_) { - return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; - } else if (goog.userAgent.IE || goog.userAgent.OPERA) { - return el.getAttribute('unselectable') == 'on'; - } - return false; -}; - - -/** - * Makes the element and its descendants selectable or unselectable. Note - * that on some platforms (e.g. Mozilla), even if an element isn't set to - * be unselectable, it will behave as such if any of its ancestors is - * unselectable. - * @param {Element} el The element to alter. - * @param {boolean} unselectable Whether the element and its descendants - * should be made unselectable. - * @param {boolean=} opt_noRecurse Whether to only alter the element's own - * selectable state, and leave its descendants alone; defaults to false. - */ -goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { - // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure? - var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; - var name = goog.style.unselectableStyle_; - if (name) { - // Add/remove the appropriate CSS style to/from the element and its - // descendants. - var value = unselectable ? 'none' : ''; - // MathML elements do not have a style property. Verify before setting. - if (el.style) { - el.style[name] = value; - } - if (descendants) { - for (var i = 0, descendant; descendant = descendants[i]; i++) { - if (descendant.style) { - descendant.style[name] = value; - } - } - } - } else if (goog.userAgent.IE || goog.userAgent.OPERA) { - // Toggle the 'unselectable' attribute on the element and its descendants. - var value = unselectable ? 'on' : ''; - el.setAttribute('unselectable', value); - if (descendants) { - for (var i = 0, descendant; descendant = descendants[i]; i++) { - descendant.setAttribute('unselectable', value); - } - } - } -}; +ol.render.VectorContext.prototype.drawPolygon = goog.abstractMethod; /** - * Gets the border box size for an element. - * @param {Element} element The element to get the size for. - * @return {!goog.math.Size} The border box size. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ -goog.style.getBorderBoxSize = function(element) { - return new goog.math.Size( - /** @type {!HTMLElement} */ (element).offsetWidth, - /** @type {!HTMLElement} */ (element).offsetHeight); -}; +ol.render.VectorContext.prototype.drawText = goog.abstractMethod; /** - * Sets the border box size of an element. This is potentially expensive in IE - * if the document is CSS1Compat mode - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size. + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. */ -goog.style.setBorderBoxSize = function(element, size) { - var doc = goog.dom.getOwnerDocument(element); - var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); - - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') && - (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { - var style = element.style; - if (isCss1CompatMode) { - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - style.pixelWidth = size.width - borderBox.left - paddingBox.left - - paddingBox.right - borderBox.right; - style.pixelHeight = size.height - borderBox.top - paddingBox.top - - paddingBox.bottom - borderBox.bottom; - } else { - style.pixelWidth = size.width; - style.pixelHeight = size.height; - } - } else { - goog.style.setBoxSizingSize_(element, size, 'border-box'); - } -}; +ol.render.VectorContext.prototype.setFillStrokeStyle = goog.abstractMethod; /** - * Gets the content box size for an element. This is potentially expensive in - * all browsers. - * @param {Element} element The element to get the size for. - * @return {!goog.math.Size} The content box size. + * @param {ol.style.Image} imageStyle Image style. */ -goog.style.getContentBoxSize = function(element) { - var doc = goog.dom.getOwnerDocument(element); - var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; - if (ieCurrentStyle && goog.dom.getDomHelper(doc).isCss1CompatMode() && - ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && - !ieCurrentStyle.boxSizing) { - // If IE in CSS1Compat mode than just use the width and height. - // If we have a boxSizing then fall back on measuring the borders etc. - var width = goog.style.getIePixelValue_( - element, ieCurrentStyle.width, 'width', 'pixelWidth'); - var height = goog.style.getIePixelValue_( - element, ieCurrentStyle.height, 'height', 'pixelHeight'); - return new goog.math.Size(width, height); - } else { - var borderBoxSize = goog.style.getBorderBoxSize(element); - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - return new goog.math.Size( - borderBoxSize.width - borderBox.left - paddingBox.left - - paddingBox.right - borderBox.right, - borderBoxSize.height - borderBox.top - paddingBox.top - - paddingBox.bottom - borderBox.bottom); - } -}; +ol.render.VectorContext.prototype.setImageStyle = goog.abstractMethod; /** - * Sets the content box size of an element. This is potentially expensive in IE - * if the document is BackCompat mode. - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size. + * @param {ol.style.Text} textStyle Text style. */ -goog.style.setContentBoxSize = function(element, size) { - var doc = goog.dom.getOwnerDocument(element); - var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') && - (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { - var style = element.style; - if (isCss1CompatMode) { - style.pixelWidth = size.width; - style.pixelHeight = size.height; - } else { - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - style.pixelWidth = size.width + borderBox.left + paddingBox.left + - paddingBox.right + borderBox.right; - style.pixelHeight = size.height + borderBox.top + paddingBox.top + - paddingBox.bottom + borderBox.bottom; - } - } else { - goog.style.setBoxSizingSize_(element, size, 'content-box'); - } -}; - +ol.render.VectorContext.prototype.setTextStyle = goog.abstractMethod; -/** - * Helper function that sets the box sizing as well as the width and height - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size to set. - * @param {string} boxSizing The box-sizing value. - * @private - */ -goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { - var style = element.style; - if (goog.userAgent.GECKO) { - style.MozBoxSizing = boxSizing; - } else if (goog.userAgent.WEBKIT) { - style.WebkitBoxSizing = boxSizing; - } else { - // Includes IE8 and Opera 9.50+ - style.boxSizing = boxSizing; - } +goog.provide('ol.render.Event'); +goog.provide('ol.render.EventType'); - // Setting this to a negative value will throw an exception on IE - // (and doesn't do anything different than setting it to 0). - style.width = Math.max(size.width, 0) + 'px'; - style.height = Math.max(size.height, 0) + 'px'; -}; +goog.require('ol.events.Event'); +goog.require('ol.render.VectorContext'); /** - * IE specific function that converts a non pixel unit to pixels. - * @param {Element} element The element to convert the value for. - * @param {string} value The current value as a string. The value must not be - * ''. - * @param {string} name The CSS property name to use for the converstion. This - * should be 'left', 'top', 'width' or 'height'. - * @param {string} pixelName The CSS pixel property name to use to get the - * value in pixels. - * @return {number} The value in pixels. - * @private + * @enum {string} */ -goog.style.getIePixelValue_ = function(element, value, name, pixelName) { - // Try if we already have a pixel value. IE does not do half pixels so we - // only check if it matches a number followed by 'px'. - if (/^\d+px?$/.test(value)) { - return parseInt(value, 10); - } else { - var oldStyleValue = element.style[name]; - var oldRuntimeValue = element.runtimeStyle[name]; - // set runtime style to prevent changes - element.runtimeStyle[name] = element.currentStyle[name]; - element.style[name] = value; - var pixelValue = element.style[pixelName]; - // restore - element.style[name] = oldStyleValue; - element.runtimeStyle[name] = oldRuntimeValue; - return pixelValue; - } +ol.render.EventType = { + /** + * @event ol.render.Event#postcompose + * @api + */ + POSTCOMPOSE: 'postcompose', + /** + * @event ol.render.Event#precompose + * @api + */ + PRECOMPOSE: 'precompose', + /** + * @event ol.render.Event#render + * @api + */ + RENDER: 'render' }; /** - * Helper function for getting the pixel padding or margin for IE. - * @param {Element} element The element to get the padding for. - * @param {string} propName The property name. - * @return {number} The pixel padding. - * @private + * @constructor + * @extends {ol.events.Event} + * @implements {oli.render.Event} + * @param {ol.render.EventType} type Type. + * @param {Object=} opt_target Target. + * @param {ol.render.VectorContext=} opt_vectorContext Vector context. + * @param {olx.FrameState=} opt_frameState Frame state. + * @param {?CanvasRenderingContext2D=} opt_context Context. + * @param {?ol.webgl.Context=} opt_glContext WebGL Context. */ -goog.style.getIePixelDistance_ = function(element, propName) { - var value = goog.style.getCascadedStyle(element, propName); - return value ? - goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : - 0; -}; - +ol.render.Event = function( + type, opt_target, opt_vectorContext, opt_frameState, opt_context, + opt_glContext) { -/** - * Gets the computed paddings or margins (on all sides) in pixels. - * @param {Element} element The element to get the padding for. - * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, - * or 'margin' to retrieve the margin box. - * @return {!goog.math.Box} The computed paddings or margins. - * @private - */ -goog.style.getBox_ = function(element, stylePrefix) { - if (goog.userAgent.IE) { - var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); - var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); - var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); - var bottom = - goog.style.getIePixelDistance_(element, stylePrefix + 'Bottom'); - return new goog.math.Box(top, right, bottom, left); - } else { - // On non-IE browsers, getComputedStyle is always non-null. - var left = goog.style.getComputedStyle(element, stylePrefix + 'Left'); - var right = goog.style.getComputedStyle(element, stylePrefix + 'Right'); - var top = goog.style.getComputedStyle(element, stylePrefix + 'Top'); - var bottom = goog.style.getComputedStyle(element, stylePrefix + 'Bottom'); + ol.events.Event.call(this, type, opt_target); - // NOTE(arv): Gecko can return floating point numbers for the computed - // style values. - return new goog.math.Box( - parseFloat(top), parseFloat(right), parseFloat(bottom), - parseFloat(left)); - } -}; + /** + * For canvas, this is an instance of {@link ol.render.canvas.Immediate}. + * @type {ol.render.VectorContext|undefined} + * @api + */ + this.vectorContext = opt_vectorContext; + /** + * An object representing the current render frame state. + * @type {olx.FrameState|undefined} + * @api + */ + this.frameState = opt_frameState; -/** - * Gets the computed paddings (on all sides) in pixels. - * @param {Element} element The element to get the padding for. - * @return {!goog.math.Box} The computed paddings. - */ -goog.style.getPaddingBox = function(element) { - return goog.style.getBox_(element, 'padding'); -}; + /** + * Canvas context. Only available when a Canvas renderer is used, null + * otherwise. + * @type {CanvasRenderingContext2D|null|undefined} + * @api + */ + this.context = opt_context; + /** + * WebGL context. Only available when a WebGL renderer is used, null + * otherwise. + * @type {ol.webgl.Context|null|undefined} + * @api + */ + this.glContext = opt_glContext; -/** - * Gets the computed margins (on all sides) in pixels. - * @param {Element} element The element to get the margins for. - * @return {!goog.math.Box} The computed margins. - */ -goog.style.getMarginBox = function(element) { - return goog.style.getBox_(element, 'margin'); }; +ol.inherits(ol.render.Event, ol.events.Event); +goog.provide('ol.layer.Layer'); -/** - * A map used to map the border width keywords to a pixel width. - * @type {!Object} - * @private - */ -goog.style.ieBorderWidthKeywords_ = { - 'thin': 2, - 'medium': 4, - 'thick': 6 -}; +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.layer.Base'); +goog.require('ol.layer.LayerProperty'); +goog.require('ol.object'); +goog.require('ol.render.EventType'); +goog.require('ol.source.State'); /** - * Helper function for IE to get the pixel border. - * @param {Element} element The element to get the pixel border for. - * @param {string} prop The part of the property name. - * @return {number} The value in pixels. - * @private + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * A visual representation of raster or vector map data. + * Layers group together those properties that pertain to how the data is to be + * displayed, irrespective of the source of that data. + * + * Layers are usually added to a map with {@link ol.Map#addLayer}. Components + * like {@link ol.interaction.Select} use unmanaged layers internally. These + * unmanaged layers are associated with the map using + * {@link ol.layer.Layer#setMap} instead. + * + * A generic `change` event is fired when the state of the source changes. + * + * @constructor + * @extends {ol.layer.Base} + * @fires ol.render.Event + * @param {olx.layer.LayerOptions} options Layer options. + * @api stable */ -goog.style.getIePixelBorder_ = function(element, prop) { - if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { - return 0; - } - var width = goog.style.getCascadedStyle(element, prop + 'Width'); - if (width in goog.style.ieBorderWidthKeywords_) { - return goog.style.ieBorderWidthKeywords_[width]; - } - return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); -}; +ol.layer.Layer = function(options) { + var baseOptions = ol.object.assign({}, options); + delete baseOptions.source; -/** - * Gets the computed border widths (on all sides) in pixels - * @param {Element} element The element to get the border widths for. - * @return {!goog.math.Box} The computed border widths. - */ -goog.style.getBorderBox = function(element) { - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { - var left = goog.style.getIePixelBorder_(element, 'borderLeft'); - var right = goog.style.getIePixelBorder_(element, 'borderRight'); - var top = goog.style.getIePixelBorder_(element, 'borderTop'); - var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); - return new goog.math.Box(top, right, bottom, left); - } else { - // On non-IE browsers, getComputedStyle is always non-null. - var left = goog.style.getComputedStyle(element, 'borderLeftWidth'); - var right = goog.style.getComputedStyle(element, 'borderRightWidth'); - var top = goog.style.getComputedStyle(element, 'borderTopWidth'); - var bottom = goog.style.getComputedStyle(element, 'borderBottomWidth'); + ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions)); - return new goog.math.Box( - parseFloat(top), parseFloat(right), parseFloat(bottom), - parseFloat(left)); - } -}; + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapPrecomposeKey_ = null; + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapRenderKey_ = null; -/** - * Returns the font face applied to a given node. Opera and IE should return - * the font actually displayed. Firefox returns the author's most-preferred - * font (whether the browser is capable of displaying it or not.) - * @param {Element} el The element whose font family is returned. - * @return {string} The font family applied to el. - */ -goog.style.getFontFamily = function(el) { - var doc = goog.dom.getOwnerDocument(el); - var font = ''; - // The moveToElementText method from the TextRange only works if the element - // is attached to the owner document. - if (doc.body.createTextRange && goog.dom.contains(doc, el)) { - var range = doc.body.createTextRange(); - range.moveToElementText(el); - /** @preserveTry */ - try { - font = range.queryCommandValue('FontName'); - } catch (e) { - // This is a workaround for a awkward exception. - // On some IE, there is an exception coming from it. - // The error description from this exception is: - // This window has already been registered as a drop target - // This is bogus description, likely due to a bug in ie. - font = ''; - } - } - if (!font) { - // Note if for some reason IE can't derive FontName with a TextRange, we - // fallback to using currentStyle - font = goog.style.getStyle_(el, 'fontFamily'); + /** + * @private + * @type {?ol.EventsKey} + */ + this.sourceChangeKey_ = null; + + if (options.map) { + this.setMap(options.map); } - // Firefox returns the applied font-family string (author's list of - // preferred fonts.) We want to return the most-preferred font, in lieu of - // the *actually* applied font. - var fontsArray = font.split(','); - if (fontsArray.length > 1) font = fontsArray[0]; + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE), + this.handleSourcePropertyChange_, this); - // Sanitize for x-browser consistency: - // Strip quotes because browsers aren't consistent with how they're - // applied; Opera always encloses, Firefox sometimes, and IE never. - return goog.string.stripQuotes(font, '"\''); + var source = options.source ? options.source : null; + this.setSource(source); }; +ol.inherits(ol.layer.Layer, ol.layer.Base); /** - * Regular expression used for getLengthUnits. - * @type {RegExp} - * @private + * Return `true` if the layer is visible, and if the passed resolution is + * between the layer's minResolution and maxResolution. The comparison is + * inclusive for `minResolution` and exclusive for `maxResolution`. + * @param {ol.LayerState} layerState Layer state. + * @param {number} resolution Resolution. + * @return {boolean} The layer is visible at the given resolution. */ -goog.style.lengthUnitRegex_ = /[^\d]+$/; +ol.layer.Layer.visibleAtResolution = function(layerState, resolution) { + return layerState.visible && resolution >= layerState.minResolution && + resolution < layerState.maxResolution; +}; /** - * Returns the units used for a CSS length measurement. - * @param {string} value A CSS length quantity. - * @return {?string} The units of measurement. + * @inheritDoc */ -goog.style.getLengthUnits = function(value) { - var units = value.match(goog.style.lengthUnitRegex_); - return units && units[0] || null; +ol.layer.Layer.prototype.getLayersArray = function(opt_array) { + var array = opt_array ? opt_array : []; + array.push(this); + return array; }; /** - * Map of absolute CSS length units - * @type {!Object} - * @private + * @inheritDoc */ -goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { - 'cm': 1, - 'in': 1, - 'mm': 1, - 'pc': 1, - 'pt': 1 +ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) { + var states = opt_states ? opt_states : []; + states.push(this.getLayerState()); + return states; }; /** - * Map of relative CSS length units that can be accurately converted to px - * font-size values using getIePixelValue_. Only units that are defined in - * relation to a font size are convertible (%, small, etc. are not). - * @type {!Object} - * @private + * Get the layer source. + * @return {ol.source.Source} The layer source (or `null` if not yet set). + * @observable + * @api stable */ -goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { - 'em': 1, - 'ex': 1 +ol.layer.Layer.prototype.getSource = function() { + var source = this.get(ol.layer.LayerProperty.SOURCE); + return /** @type {ol.source.Source} */ (source) || null; }; /** - * Returns the font size, in pixels, of text in an element. - * @param {Element} el The element whose font size is returned. - * @return {number} The font size (in pixels). - */ -goog.style.getFontSize = function(el) { - var fontSize = goog.style.getStyle_(el, 'fontSize'); - var sizeUnits = goog.style.getLengthUnits(fontSize); - if (fontSize && 'px' == sizeUnits) { - // NOTE(user): This could be parseFloat instead, but IE doesn't return - // decimal fractions in getStyle_ and Firefox reports the fractions, but - // ignores them when rendering. Interestingly enough, when we force the - // issue and size something to e.g., 50% of 25px, the browsers round in - // opposite directions with Firefox reporting 12px and IE 13px. I punt. - return parseInt(fontSize, 10); - } - - // In IE, we can convert absolute length units to a px value using - // goog.style.getIePixelValue_. Units defined in relation to a font size - // (em, ex) are applied relative to the element's parentNode and can also - // be converted. - if (goog.userAgent.IE) { - if (String(sizeUnits) in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { - return goog.style.getIePixelValue_(el, fontSize, 'left', 'pixelLeft'); - } else if ( - el.parentNode && el.parentNode.nodeType == goog.dom.NodeType.ELEMENT && - String(sizeUnits) in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { - // Check the parent size - if it is the same it means the relative size - // value is inherited and we therefore don't want to count it twice. If - // it is different, this element either has explicit style or has a CSS - // rule applying to it. - var parentElement = /** @type {!Element} */ (el.parentNode); - var parentSize = goog.style.getStyle_(parentElement, 'fontSize'); - return goog.style.getIePixelValue_( - parentElement, fontSize == parentSize ? '1em' : fontSize, 'left', - 'pixelLeft'); - } - } - - // Sometimes we can't cleanly find the font size (some units relative to a - // node's parent's font size are difficult: %, smaller et al), so we create - // an invisible, absolutely-positioned span sized to be the height of an 'M' - // rendered in its parent's (i.e., our target element's) font size. This is - // the definition of CSS's font size attribute. - var sizeElement = goog.dom.createDom(goog.dom.TagName.SPAN, { - 'style': 'visibility:hidden;position:absolute;' + - 'line-height:0;padding:0;margin:0;border:0;height:1em;' - }); - goog.dom.appendChild(el, sizeElement); - fontSize = sizeElement.offsetHeight; - goog.dom.removeNode(sizeElement); - - return fontSize; + * @inheritDoc + */ +ol.layer.Layer.prototype.getSourceState = function() { + var source = this.getSource(); + return !source ? ol.source.State.UNDEFINED : source.getState(); }; /** - * Parses a style attribute value. Converts CSS property names to camel case. - * @param {string} value The style attribute value. - * @return {!Object} Map of CSS properties to string values. + * @private */ -goog.style.parseStyleAttribute = function(value) { - var result = {}; - goog.array.forEach(value.split(/\s*;\s*/), function(pair) { - var keyValue = pair.match(/\s*([\w-]+)\s*\:(.+)/); - if (keyValue) { - var styleName = keyValue[1]; - var styleValue = goog.string.trim(keyValue[2]); - result[goog.string.toCamelCase(styleName.toLowerCase())] = styleValue; - } - }); - return result; +ol.layer.Layer.prototype.handleSourceChange_ = function() { + this.changed(); }; /** - * Reverse of parseStyleAttribute; that is, takes a style object and returns the - * corresponding attribute value. Converts camel case property names to proper - * CSS selector names. - * @param {Object} obj Map of CSS properties to values. - * @return {string} The style attribute value. + * @private */ -goog.style.toStyleAttribute = function(obj) { - var buffer = []; - goog.object.forEach(obj, function(value, key) { - buffer.push(goog.string.toSelectorCase(key), ':', value, ';'); - }); - return buffer.join(''); -}; - - -/** - * Sets CSS float property on an element. - * @param {Element} el The element to set float property on. - * @param {string} value The value of float CSS property to set on this element. - */ -goog.style.setFloat = function(el, value) { - el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; -}; - - -/** - * Gets value of explicitly-set float CSS property on an element. - * @param {Element} el The element to get float property of. - * @return {string} The value of explicitly-set float CSS property on this - * element. - */ -goog.style.getFloat = function(el) { - return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; +ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { + if (this.sourceChangeKey_) { + ol.events.unlistenByKey(this.sourceChangeKey_); + this.sourceChangeKey_ = null; + } + var source = this.getSource(); + if (source) { + this.sourceChangeKey_ = ol.events.listen(source, + ol.events.EventType.CHANGE, this.handleSourceChange_, this); + } + this.changed(); }; /** - * Returns the scroll bar width (represents the width of both horizontal - * and vertical scroll). + * Sets the layer to be rendered on top of other layers on a map. The map will + * not manage this layer in its layers collection, and the callback in + * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. This + * is useful for temporary layers. To remove an unmanaged layer from the map, + * use `#setMap(null)`. * - * @param {string=} opt_className An optional class name (or names) to apply - * to the invisible div created to measure the scrollbar. This is necessary - * if some scrollbars are styled differently than others. - * @return {number} The scroll bar width in px. + * To add the layer to a map and have it managed by the map, use + * {@link ol.Map#addLayer} instead. + * @param {ol.Map} map Map. + * @api */ -goog.style.getScrollbarWidth = function(opt_className) { - // Add two hidden divs. The child div is larger than the parent and - // forces scrollbars to appear on it. - // Using overflow:scroll does not work consistently with scrollbars that - // are styled with ::-webkit-scrollbar. - var outerDiv = goog.dom.createElement(goog.dom.TagName.DIV); - if (opt_className) { - outerDiv.className = opt_className; +ol.layer.Layer.prototype.setMap = function(map) { + if (this.mapPrecomposeKey_) { + ol.events.unlistenByKey(this.mapPrecomposeKey_); + this.mapPrecomposeKey_ = null; + } + if (!map) { + this.changed(); + } + if (this.mapRenderKey_) { + ol.events.unlistenByKey(this.mapRenderKey_); + this.mapRenderKey_ = null; + } + if (map) { + this.mapPrecomposeKey_ = ol.events.listen( + map, ol.render.EventType.PRECOMPOSE, function(evt) { + var layerState = this.getLayerState(); + layerState.managed = false; + layerState.zIndex = Infinity; + evt.frameState.layerStatesArray.push(layerState); + evt.frameState.layerStates[goog.getUid(this)] = layerState; + }, this); + this.mapRenderKey_ = ol.events.listen( + this, ol.events.EventType.CHANGE, map.render, map); + this.changed(); } - outerDiv.style.cssText = 'overflow:auto;' + - 'position:absolute;top:0;width:100px;height:100px'; - var innerDiv = goog.dom.createElement(goog.dom.TagName.DIV); - goog.style.setSize(innerDiv, '200px', '200px'); - outerDiv.appendChild(innerDiv); - goog.dom.appendChild(goog.dom.getDocument().body, outerDiv); - var width = outerDiv.offsetWidth - outerDiv.clientWidth; - goog.dom.removeNode(outerDiv); - return width; }; /** - * Regular expression to extract x and y translation components from a CSS - * transform Matrix representation. - * - * @type {!RegExp} - * @const - * @private - */ -goog.style.MATRIX_TRANSLATION_REGEX_ = new RegExp( - 'matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' + - '[0-9\\.\\-]+, [0-9\\.\\-]+, ' + - '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)'); - - -/** - * Returns the x,y translation component of any CSS transforms applied to the - * element, in pixels. - * - * @param {!Element} element The element to get the translation of. - * @return {!goog.math.Coordinate} The CSS translation of the element in px. + * Set the layer source. + * @param {ol.source.Source} source The layer source. + * @observable + * @api stable */ -goog.style.getCssTranslation = function(element) { - var transform = goog.style.getComputedTransform(element); - if (!transform) { - return new goog.math.Coordinate(0, 0); - } - var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_); - if (!matches) { - return new goog.math.Coordinate(0, 0); - } - return new goog.math.Coordinate( - parseFloat(matches[1]), parseFloat(matches[2])); +ol.layer.Layer.prototype.setSource = function(source) { + this.set(ol.layer.LayerProperty.SOURCE, source); }; -goog.provide('ol.MapEvent'); -goog.provide('ol.MapEventType'); +goog.provide('ol.ImageBase'); +goog.provide('ol.ImageState'); -goog.require('ol.events.Event'); +goog.require('goog.asserts'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); +goog.require('ol.Attribution'); /** - * @enum {string} + * @enum {number} */ -ol.MapEventType = { - - /** - * Triggered after a map frame is rendered. - * @event ol.MapEvent#postrender - * @api - */ - POSTRENDER: 'postrender', - - /** - * Triggered after the map is moved. - * @event ol.MapEvent#moveend - * @api stable - */ - MOVEEND: 'moveend' - +ol.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 }; /** - * @classdesc - * Events emitted as map events are instances of this type. - * See {@link ol.Map} for which events trigger a map event. - * * @constructor - * @extends {ol.events.Event} - * @implements {oli.MapEvent} - * @param {string} type Event type. - * @param {ol.Map} map Map. - * @param {?olx.FrameState=} opt_frameState Frame state. + * @extends {ol.events.EventTarget} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.ImageState} state State. + * @param {Array.} attributions Attributions. */ -ol.MapEvent = function(type, map, opt_frameState) { - - goog.base(this, type); +ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) { - /** - * The map where the event occurred. - * @type {ol.Map} - * @api stable - */ - this.map = map; + ol.events.EventTarget.call(this); /** - * The frame state at the time of the event. - * @type {?olx.FrameState} - * @api + * @private + * @type {Array.} */ - this.frameState = opt_frameState !== undefined ? opt_frameState : null; - -}; -goog.inherits(ol.MapEvent, ol.events.Event); - -goog.provide('ol.control.Control'); - -goog.require('goog.dom'); -goog.require('ol.events'); -goog.require('ol'); -goog.require('ol.MapEventType'); -goog.require('ol.Object'); - - -/** - * @classdesc - * A control is a visible widget with a DOM element in a fixed position on the - * screen. They can involve user input (buttons), or be informational only; - * the position is determined using CSS. By default these are placed in the - * container with CSS class name `ol-overlaycontainer-stopevent`, but can use - * any outside DOM element. - * - * This is the base class for controls. You can use it for simple custom - * controls by creating the element with listeners, creating an instance: - * ```js - * var myControl = new ol.control.Control({element: myElement}); - * ``` - * and then adding this to the map. - * - * The main advantage of having this as a control rather than a simple separate - * DOM element is that preventing propagation is handled for you. Controls - * will also be `ol.Object`s in a `ol.Collection`, so you can use their - * methods. - * - * You can also extend this base for your own control class. See - * examples/custom-controls for an example of how to do this. - * - * @constructor - * @extends {ol.Object} - * @implements {oli.control.Control} - * @param {olx.control.ControlOptions} options Control options. - * @api stable - */ -ol.control.Control = function(options) { - - goog.base(this); + this.attributions_ = attributions; /** * @protected - * @type {Element} + * @type {ol.Extent} */ - this.element = options.element ? options.element : null; + this.extent = extent; /** * @private - * @type {Element} + * @type {number} */ - this.target_ = null; + this.pixelRatio_ = pixelRatio; /** - * @private - * @type {ol.Map} + * @protected + * @type {number|undefined} */ - this.map_ = null; + this.resolution = resolution; /** * @protected - * @type {!Array.} + * @type {ol.ImageState} */ - this.listenerKeys = []; + this.state = state; - /** - * @type {function(ol.MapEvent)} - */ - this.render = options.render ? options.render : ol.nullFunction; +}; +ol.inherits(ol.ImageBase, ol.events.EventTarget); - if (options.target) { - this.setTarget(options.target); - } +/** + * @protected + */ +ol.ImageBase.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); }; -goog.inherits(ol.control.Control, ol.Object); /** - * @inheritDoc + * @return {Array.} Attributions. */ -ol.control.Control.prototype.disposeInternal = function() { - goog.dom.removeNode(this.element); - goog.base(this, 'disposeInternal'); +ol.ImageBase.prototype.getAttributions = function() { + return this.attributions_; }; /** - * Get the map associated with this control. - * @return {ol.Map} Map. - * @api stable + * @return {ol.Extent} Extent. */ -ol.control.Control.prototype.getMap = function() { - return this.map_; +ol.ImageBase.prototype.getExtent = function() { + return this.extent; }; /** - * Remove the control from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {ol.Map} map Map. - * @api stable + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. */ -ol.control.Control.prototype.setMap = function(map) { - if (this.map_) { - goog.dom.removeNode(this.element); - } - for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { - ol.events.unlistenByKey(this.listenerKeys[i]); - } - this.listenerKeys.length = 0; - this.map_ = map; - if (this.map_) { - var target = this.target_ ? - this.target_ : map.getOverlayContainerStopEvent(); - target.appendChild(this.element); - if (this.render !== ol.nullFunction) { - this.listenerKeys.push(ol.events.listen(map, - ol.MapEventType.POSTRENDER, this.render, this)); - } - map.render(); - } +ol.ImageBase.prototype.getImage = goog.abstractMethod; + + +/** + * @return {number} PixelRatio. + */ +ol.ImageBase.prototype.getPixelRatio = function() { + return this.pixelRatio_; }; /** - * This function is used to set a target element for the control. It has no - * effect if it is called after the control has been added to the map (i.e. - * after `setMap` is called on the control). If no `target` is set in the - * options passed to the control constructor and if `setTarget` is not called - * then the control is added to the map's overlay container. - * @param {Element|string} target Target. - * @api + * @return {number} Resolution. */ -ol.control.Control.prototype.setTarget = function(target) { - this.target_ = goog.dom.getElement(target); +ol.ImageBase.prototype.getResolution = function() { + goog.asserts.assert(this.resolution !== undefined, 'resolution not yet set'); + return this.resolution; }; -goog.provide('ol.css'); + +/** + * @return {ol.ImageState} State. + */ +ol.ImageBase.prototype.getState = function() { + return this.state; +}; /** - * The CSS class for hidden feature. - * - * @const - * @type {string} + * Load not yet loaded URI. */ -ol.css.CLASS_HIDDEN = 'ol-hidden'; +ol.ImageBase.prototype.load = goog.abstractMethod; + +goog.provide('ol.vec.Mat4'); +goog.provide('ol.vec.Mat4.Number'); + +goog.require('goog.vec.Mat4'); /** - * The CSS class that we'll give the DOM elements to have them unselectable. - * - * @const - * @type {string} + * A alias for the goog.vec.Number type. + * @typedef {goog.vec.Number} */ -ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; +ol.vec.Mat4.Number; /** - * The CSS class for unsupported feature. - * - * @const - * @type {string} + * @param {!goog.vec.Mat4.Number} mat Matrix. + * @param {number} translateX1 Translate X1. + * @param {number} translateY1 Translate Y1. + * @param {number} scaleX Scale X. + * @param {number} scaleY Scale Y. + * @param {number} rotation Rotation. + * @param {number} translateX2 Translate X2. + * @param {number} translateY2 Translate Y2. + * @return {!goog.vec.Mat4.Number} Matrix. */ -ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; +ol.vec.Mat4.makeTransform2D = function(mat, translateX1, translateY1, + scaleX, scaleY, rotation, translateX2, translateY2) { + goog.vec.Mat4.makeIdentity(mat); + if (translateX1 !== 0 || translateY1 !== 0) { + goog.vec.Mat4.translate(mat, translateX1, translateY1, 0); + } + if (scaleX != 1 || scaleY != 1) { + goog.vec.Mat4.scale(mat, scaleX, scaleY, 1); + } + if (rotation !== 0) { + goog.vec.Mat4.rotateZ(mat, rotation); + } + if (translateX2 !== 0 || translateY2 !== 0) { + goog.vec.Mat4.translate(mat, translateX2, translateY2, 0); + } + return mat; +}; /** - * The CSS class for controls. + * Returns true if mat1 and mat2 represent the same 2D transformation. + * @param {goog.vec.Mat4.Number} mat1 Matrix 1. + * @param {goog.vec.Mat4.Number} mat2 Matrix 2. + * @return {boolean} Equal 2D. + */ +ol.vec.Mat4.equals2D = function(mat1, mat2) { + return ( + goog.vec.Mat4.getElement(mat1, 0, 0) == + goog.vec.Mat4.getElement(mat2, 0, 0) && + goog.vec.Mat4.getElement(mat1, 1, 0) == + goog.vec.Mat4.getElement(mat2, 1, 0) && + goog.vec.Mat4.getElement(mat1, 0, 1) == + goog.vec.Mat4.getElement(mat2, 0, 1) && + goog.vec.Mat4.getElement(mat1, 1, 1) == + goog.vec.Mat4.getElement(mat2, 1, 1) && + goog.vec.Mat4.getElement(mat1, 0, 3) == + goog.vec.Mat4.getElement(mat2, 0, 3) && + goog.vec.Mat4.getElement(mat1, 1, 3) == + goog.vec.Mat4.getElement(mat2, 1, 3)); +}; + + +/** + * Transforms the given vector with the given matrix storing the resulting, + * transformed vector into resultVec. The input vector is multiplied against the + * upper 2x4 matrix omitting the projective component. * - * @const - * @type {string} + * @param {goog.vec.Mat4.Number} mat The matrix supplying the transformation. + * @param {Array.} vec The 3 element vector to transform. + * @param {Array.} resultVec The 3 element vector to receive the results + * (may be vec). + * @return {Array.} return resultVec so that operations can be + * chained together. */ -ol.css.CLASS_CONTROL = 'ol-control'; +ol.vec.Mat4.multVec2 = function(mat, vec, resultVec) { + var m00 = goog.vec.Mat4.getElement(mat, 0, 0); + var m10 = goog.vec.Mat4.getElement(mat, 1, 0); + var m01 = goog.vec.Mat4.getElement(mat, 0, 1); + var m11 = goog.vec.Mat4.getElement(mat, 1, 1); + var m03 = goog.vec.Mat4.getElement(mat, 0, 3); + var m13 = goog.vec.Mat4.getElement(mat, 1, 3); + var x = vec[0], y = vec[1]; + resultVec[0] = m00 * x + m01 * y + m03; + resultVec[1] = m10 * x + m11 * y + m13; + return resultVec; +}; -goog.provide('ol.structs.LRUCache'); +goog.provide('ol.renderer.Layer'); goog.require('goog.asserts'); -goog.require('ol.object'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.ImageState'); +goog.require('ol.Observable'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.layer.Layer'); +goog.require('ol.source.Source'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); +goog.require('ol.vec.Mat4'); /** - * Implements a Least-Recently-Used cache where the keys do not conflict with - * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring - * items from the cache is the responsibility of the user. * @constructor + * @extends {ol.Observable} + * @param {ol.layer.Layer} layer Layer. * @struct - * @template T */ -ol.structs.LRUCache = function() { - - /** - * @private - * @type {number} - */ - this.count_ = 0; +ol.renderer.Layer = function(layer) { - /** - * @private - * @type {!Object.} - */ - this.entries_ = {}; + ol.Observable.call(this); /** * @private - * @type {?ol.LRUCacheEntry} + * @type {ol.layer.Layer} */ - this.oldest_ = null; + this.layer_ = layer; - /** - * @private - * @type {?ol.LRUCacheEntry} - */ - this.newest_ = null; }; +ol.inherits(ol.renderer.Layer, ol.Observable); /** - * FIXME empty description for jsdoc + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T} + * callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T */ -ol.structs.LRUCache.prototype.assertValid = function() { - if (this.count_ === 0) { - goog.asserts.assert(ol.object.isEmpty(this.entries_), - 'entries must be an empty object (count = 0)'); - goog.asserts.assert(!this.oldest_, - 'oldest must be null (count = 0)'); - goog.asserts.assert(!this.newest_, - 'newest must be null (count = 0)'); - } else { - goog.asserts.assert(Object.keys(this.entries_).length == this.count_, - 'number of entries matches count'); - goog.asserts.assert(this.oldest_, - 'we have an oldest entry'); - goog.asserts.assert(!this.oldest_.older, - 'no entry is older than oldest'); - goog.asserts.assert(this.newest_, - 'we have a newest entry'); - goog.asserts.assert(!this.newest_.newer, - 'no entry is newer than newest'); - var i, entry; - var older = null; - i = 0; - for (entry = this.oldest_; entry; entry = entry.newer) { - goog.asserts.assert(entry.older === older, - 'entry.older links to correct older'); - older = entry; - ++i; - } - goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); - var newer = null; - i = 0; - for (entry = this.newest_; entry; entry = entry.older) { - goog.asserts.assert(entry.newer === newer, - 'entry.newer links to correct newer'); - newer = entry; - ++i; - } - goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); - } -}; +ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction; /** - * FIXME empty description for jsdoc + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState Frame state. + * @param {function(this: S, ol.layer.Layer): T} callback Layer callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T */ -ol.structs.LRUCache.prototype.clear = function() { - this.count_ = 0; - this.entries_ = {}; - this.oldest_ = null; - this.newest_ = null; +ol.renderer.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + var coordinate = pixel.slice(); + ol.vec.Mat4.multVec2( + frameState.pixelToCoordinateMatrix, coordinate, coordinate); + + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, ol.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.layer_); + } else { + return undefined; + } }; /** - * @param {string} key Key. - * @return {boolean} Contains key. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @return {boolean} Is there a feature at the given coordinate? */ -ol.structs.LRUCache.prototype.containsKey = function(key) { - return this.entries_.hasOwnProperty(key); -}; +ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE; /** - * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function - * to call for every entry from the oldest to the newer. This function takes - * 3 arguments (the entry value, the entry key and the LRUCache object). - * The return value is ignored. - * @param {S=} opt_this The object to use as `this` in `f`. - * @template S - */ -ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { - var entry = this.oldest_; - while (entry) { - f.call(opt_this, entry.value_, entry.key_, this); - entry = entry.newer; - } + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {ol.proj.Projection} projection Projection of the tiles. + * @param {Object.>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected + */ +ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); }; /** - * @param {string} key Key. - * @return {T} Value. + * @return {ol.layer.Layer} Layer. */ -ol.structs.LRUCache.prototype.get = function(key) { - var entry = this.entries_[key]; - goog.asserts.assert(entry !== undefined, 'an entry exists for key %s', key); - if (entry === this.newest_) { - return entry.value_; - } else if (entry === this.oldest_) { - this.oldest_ = this.oldest_.newer; - this.oldest_.older = null; - } else { - entry.newer.older = entry.older; - entry.older.newer = entry.newer; - } - entry.newer = null; - entry.older = this.newest_; - this.newest_.newer = entry; - this.newest_ = entry; - return entry.value_; +ol.renderer.Layer.prototype.getLayer = function() { + return this.layer_; }; /** - * @return {number} Count. + * Handle changes in image state. + * @param {ol.events.Event} event Image change event. + * @private */ -ol.structs.LRUCache.prototype.getCount = function() { - return this.count_; +ol.renderer.Layer.prototype.handleImageChange_ = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.ImageState.LOADED) { + this.renderIfReadyAndVisible(); + } }; /** - * @return {Array.} Keys. + * Load the image if not already loaded, and register the image change + * listener if needed. + * @param {ol.ImageBase} image Image. + * @return {boolean} `true` if the image is already loaded, `false` + * otherwise. + * @protected */ -ol.structs.LRUCache.prototype.getKeys = function() { - var keys = new Array(this.count_); - var i = 0; - var entry; - for (entry = this.newest_; entry; entry = entry.older) { - keys[i++] = entry.key_; +ol.renderer.Layer.prototype.loadImage = function(image) { + var imageState = image.getState(); + if (imageState != ol.ImageState.LOADED && + imageState != ol.ImageState.ERROR) { + // the image is either "idle" or "loading", register the change + // listener (a noop if the listener was already registered) + goog.asserts.assert(imageState == ol.ImageState.IDLE || + imageState == ol.ImageState.LOADING, + 'imageState is "idle" or "loading"'); + ol.events.listen(image, ol.events.EventType.CHANGE, + this.handleImageChange_, this); } - goog.asserts.assert(i == this.count_, 'iterated correct number of times'); - return keys; + if (imageState == ol.ImageState.IDLE) { + image.load(); + imageState = image.getState(); + goog.asserts.assert(imageState == ol.ImageState.LOADING || + imageState == ol.ImageState.LOADED, + 'imageState is "loading" or "loaded"'); + } + return imageState == ol.ImageState.LOADED; }; /** - * @return {Array.} Values. + * @protected */ -ol.structs.LRUCache.prototype.getValues = function() { - var values = new Array(this.count_); - var i = 0; - var entry; - for (entry = this.newest_; entry; entry = entry.older) { - values[i++] = entry.value_; +ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() { + var layer = this.getLayer(); + if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) { + this.changed(); } - goog.asserts.assert(i == this.count_, 'iterated correct number of times'); - return values; }; /** - * @return {T} Last value. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @protected */ -ol.structs.LRUCache.prototype.peekLast = function() { - goog.asserts.assert(this.oldest_, 'oldest must not be null'); - return this.oldest_.value_; +ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) { + if (tileSource.canExpireCache()) { + /** + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + */ + var postRenderFunction = function(tileSource, map, frameState) { + var tileSourceKey = goog.getUid(tileSource).toString(); + tileSource.expireCache(frameState.viewState.projection, + frameState.usedTiles[tileSourceKey]); + }.bind(null, tileSource); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } }; /** - * @return {string} Last key. + * @param {Object.} attributionsSet Attributions + * set (target). + * @param {Array.} attributions Attributions (source). + * @protected */ -ol.structs.LRUCache.prototype.peekLastKey = function() { - goog.asserts.assert(this.oldest_, 'oldest must not be null'); - return this.oldest_.key_; +ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) { + if (attributions) { + var attribution, i, ii; + for (i = 0, ii = attributions.length; i < ii; ++i) { + attribution = attributions[i]; + attributionsSet[goog.getUid(attribution).toString()] = attribution; + } + } }; /** - * @return {T} value Value. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Source} source Source. + * @protected */ -ol.structs.LRUCache.prototype.pop = function() { - goog.asserts.assert(this.oldest_, 'oldest must not be null'); - goog.asserts.assert(this.newest_, 'newest must not be null'); - var entry = this.oldest_; - goog.asserts.assert(entry.key_ in this.entries_, - 'oldest is indexed in entries'); - delete this.entries_[entry.key_]; - if (entry.newer) { - entry.newer.older = null; +ol.renderer.Layer.prototype.updateLogos = function(frameState, source) { + var logo = source.getLogo(); + if (logo !== undefined) { + if (typeof logo === 'string') { + frameState.logos[logo] = ''; + } else if (goog.isObject(logo)) { + goog.asserts.assertString(logo.href, 'logo.href is a string'); + goog.asserts.assertString(logo.src, 'logo.src is a string'); + frameState.logos[logo.src] = logo.href; + } } - this.oldest_ = entry.newer; - if (!this.oldest_) { - this.newest_ = null; +}; + + +/** + * @param {Object.>} usedTiles Used tiles. + * @param {ol.source.Tile} tileSource Tile source. + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @protected + */ +ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) { + // FIXME should we use tilesToDrawByZ instead? + var tileSourceKey = goog.getUid(tileSource).toString(); + var zKey = z.toString(); + if (tileSourceKey in usedTiles) { + if (zKey in usedTiles[tileSourceKey]) { + usedTiles[tileSourceKey][zKey].extend(tileRange); + } else { + usedTiles[tileSourceKey][zKey] = tileRange; + } + } else { + usedTiles[tileSourceKey] = {}; + usedTiles[tileSourceKey][zKey] = tileRange; } - --this.count_; - return entry.value_; }; /** - * @param {string} key Key. - * @param {T} value Value. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {ol.Size} size Size. + * @protected + * @return {ol.Coordinate} Snapped center. */ -ol.structs.LRUCache.prototype.replace = function(key, value) { - this.get(key); // update `newest_` - this.entries_[key].value_ = value; +ol.renderer.Layer.prototype.snapCenterToPixel = function(center, resolution, size) { + return [ + resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2), + resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2) + ]; }; /** - * @param {string} key Key. - * @param {T} value Value. + * Manage tile pyramid. + * This function performs a number of functions related to the tiles at the + * current zoom and lower zoom levels: + * - registers idle tiles in frameState.wantedTiles so that they are not + * discarded by the tile queue + * - enqueues missing tiles + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {ol.Extent} extent Extent. + * @param {number} currentZ Current Z. + * @param {number} preload Load low resolution tiles up to 'preload' levels. + * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback. + * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`. + * @protected + * @template T */ -ol.structs.LRUCache.prototype.set = function(key, value) { - goog.asserts.assert(!(key in {}), - 'key is not a standard property of objects (e.g. "__proto__")'); - goog.asserts.assert(!(key in this.entries_), - 'key is not used already'); - var entry = { - key_: key, - newer: null, - older: this.newest_, - value_: value - }; - if (!this.newest_) { - this.oldest_ = entry; - } else { - this.newest_.newer = entry; +ol.renderer.Layer.prototype.manageTilePyramid = function( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, + currentZ, preload, opt_tileCallback, opt_this) { + var tileSourceKey = goog.getUid(tileSource).toString(); + if (!(tileSourceKey in frameState.wantedTiles)) { + frameState.wantedTiles[tileSourceKey] = {}; + } + var wantedTiles = frameState.wantedTiles[tileSourceKey]; + var tileQueue = frameState.tileQueue; + var minZoom = tileGrid.getMinZoom(); + var tile, tileRange, tileResolution, x, y, z; + for (z = currentZ; z >= minZoom; --z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); + tileResolution = tileGrid.getResolution(z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + if (currentZ - z <= preload) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == ol.TileState.IDLE) { + wantedTiles[tile.tileCoord.toString()] = true; + if (!tileQueue.isKeyQueued(tile.getKey())) { + tileQueue.enqueue([tile, tileSourceKey, + tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); + } + } + if (opt_tileCallback !== undefined) { + opt_tileCallback.call(opt_this, tile); + } + } else { + tileSource.useTile(z, x, y, projection); + } + } + } } - this.newest_ = entry; - this.entries_[key] = entry; - ++this.count_; }; -goog.provide('ol.tilecoord'); - -goog.require('goog.asserts'); -goog.require('ol.extent'); +goog.provide('ol.style.Image'); +goog.provide('ol.style.ImageState'); /** * @enum {number} */ -ol.QuadKeyCharCode = { - ZERO: '0'.charCodeAt(0), - ONE: '1'.charCodeAt(0), - TWO: '2'.charCodeAt(0), - THREE: '3'.charCodeAt(0) +ol.style.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 }; /** - * @param {string} str String that follows pattern “z/x/y” where x, y and z are - * numbers. - * @return {ol.TileCoord} Tile coord. + * @classdesc + * A base class used for creating subclasses and not instantiated in + * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and + * {@link ol.style.RegularShape}. + * + * @constructor + * @param {ol.StyleImageOptions} options Options. + * @api */ -ol.tilecoord.createFromString = function(str) { - var v = str.split('/'); - goog.asserts.assert(v.length === 3, - 'must provide a string in "z/x/y" format, got "%s"', str); - return v.map(function(e) { - return parseInt(e, 10); - }); +ol.style.Image = function(options) { + + /** + * @private + * @type {number} + */ + this.opacity_ = options.opacity; + + /** + * @private + * @type {boolean} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {number} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {boolean} + */ + this.snapToPixel_ = options.snapToPixel; + }; /** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. - * @return {ol.TileCoord} Tile coordinate. + * Get the symbolizer opacity. + * @return {number} Opacity. + * @api */ -ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { - if (opt_tileCoord !== undefined) { - opt_tileCoord[0] = z; - opt_tileCoord[1] = x; - opt_tileCoord[2] = y; - return opt_tileCoord; - } else { - return [z, x, y]; - } +ol.style.Image.prototype.getOpacity = function() { + return this.opacity_; }; /** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @return {string} Key. + * Determine whether the symbolizer rotates with the map. + * @return {boolean} Rotate with map. + * @api */ -ol.tilecoord.getKeyZXY = function(z, x, y) { - return z + '/' + x + '/' + y; +ol.style.Image.prototype.getRotateWithView = function() { + return this.rotateWithView_; }; /** - * @param {ol.TileCoord} tileCoord Tile coord. - * @return {number} Hash. + * Get the symoblizer rotation. + * @return {number} Rotation. + * @api */ -ol.tilecoord.hash = function(tileCoord) { - return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +ol.style.Image.prototype.getRotation = function() { + return this.rotation_; }; /** - * @param {ol.TileCoord} tileCoord Tile coord. - * @return {string} Quad key. + * Get the symbolizer scale. + * @return {number} Scale. + * @api */ -ol.tilecoord.quadKey = function(tileCoord) { - var z = tileCoord[0]; - var digits = new Array(z); - var mask = 1 << (z - 1); - var i, charCode; - for (i = 0; i < z; ++i) { - charCode = ol.QuadKeyCharCode.ZERO; - if (tileCoord[1] & mask) { - charCode += 1; - } - if (tileCoord[2] & mask) { - charCode += 2; - } - digits[i] = String.fromCharCode(charCode); - mask >>= 1; - } - return digits.join(''); +ol.style.Image.prototype.getScale = function() { + return this.scale_; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. - * @param {ol.proj.Projection} projection Projection. - * @return {ol.TileCoord} Tile coordinate. + * Determine whether the symbolizer should be snapped to a pixel. + * @return {boolean} The symbolizer should snap to a pixel. + * @api */ -ol.tilecoord.wrapX = function(tileCoord, tileGrid, projection) { - var z = tileCoord[0]; - var center = tileGrid.getTileCoordCenter(tileCoord); - var projectionExtent = ol.tilegrid.extentFromProjection(projection); - if (!ol.extent.containsCoordinate(projectionExtent, center)) { - var worldWidth = ol.extent.getWidth(projectionExtent); - var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); - center[0] += worldWidth * worldsAway; - return tileGrid.getTileCoordForCoordAndZ(center, z); - } else { - return tileCoord; - } +ol.style.Image.prototype.getSnapToPixel = function() { + return this.snapToPixel_; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. - * @return {boolean} Tile coordinate is within extent and zoom level range. + * Get the anchor point. The anchor determines the center point for the + * symbolizer. Its units are determined by `anchorXUnits` and `anchorYUnits`. + * @function + * @return {Array.} Anchor. */ -ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) { - var z = tileCoord[0]; - var x = tileCoord[1]; - var y = tileCoord[2]; +ol.style.Image.prototype.getAnchor = goog.abstractMethod; - if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { - return false; - } - var extent = tileGrid.getExtent(); - var tileRange; - if (!extent) { - tileRange = tileGrid.getFullTileRange(z); - } else { - tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - } - if (!tileRange) { - return true; - } else { - return tileRange.containsXY(x, y); - } -}; -goog.provide('ol.TileCache'); +/** + * Get the image element for the symbolizer. + * @function + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getImage = goog.abstractMethod; -goog.require('ol.TileRange'); -goog.require('ol.structs.LRUCache'); -goog.require('ol.tilecoord'); + +/** + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod; /** - * @constructor - * @extends {ol.structs.LRUCache.} - * @param {number=} opt_highWaterMark High water mark. - * @struct + * @return {ol.style.ImageState} Image state. */ -ol.TileCache = function(opt_highWaterMark) { +ol.style.Image.prototype.getImageState = goog.abstractMethod; - goog.base(this); - /** - * @private - * @type {number} - */ - this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048; +/** + * @return {ol.Size} Image size. + */ +ol.style.Image.prototype.getImageSize = goog.abstractMethod; -}; -goog.inherits(ol.TileCache, ol.structs.LRUCache); + +/** + * @return {ol.Size} Size of the hit-detection image. + */ +ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod; /** - * @return {boolean} Can expire cache. + * Get the origin of the symbolizer. + * @function + * @return {Array.} Origin. */ -ol.TileCache.prototype.canExpireCache = function() { - return this.getCount() > this.highWaterMark_; -}; +ol.style.Image.prototype.getOrigin = goog.abstractMethod; /** - * @param {Object.} usedTiles Used tiles. + * Get the size of the symbolizer (in pixels). + * @function + * @return {ol.Size} Size. */ -ol.TileCache.prototype.expireCache = function(usedTiles) { - var tile, zKey; - while (this.canExpireCache()) { - tile = this.peekLast(); - zKey = tile.tileCoord[0].toString(); - if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { - break; - } else { - this.pop().dispose(); - } - } -}; +ol.style.Image.prototype.getSize = goog.abstractMethod; /** - * Remove a tile range from the cache, e.g. to invalidate tiles. - * @param {ol.TileRange} tileRange The tile range to prune. + * Set the opacity. + * + * @param {number} opacity Opacity. + * @api */ -ol.TileCache.prototype.pruneTileRange = function(tileRange) { - var i = this.getCount(), - key; - while (i--) { - key = this.peekLastKey(); - if (tileRange.contains(ol.tilecoord.createFromString(key))) { - this.pop().dispose(); - } else { - this.get(key); - } - } +ol.style.Image.prototype.setOpacity = function(opacity) { + this.opacity_ = opacity; }; -goog.provide('ol.Tile'); -goog.provide('ol.TileState'); - -goog.require('ol.events'); -goog.require('ol.events.EventTarget'); -goog.require('ol.events.EventType'); - /** - * @enum {number} + * Set whether to rotate the style with the view. + * + * @param {boolean} rotateWithView Rotate with map. */ -ol.TileState = { - IDLE: 0, - LOADING: 1, - LOADED: 2, - ERROR: 3, - EMPTY: 4, - ABORT: 5 +ol.style.Image.prototype.setRotateWithView = function(rotateWithView) { + this.rotateWithView_ = rotateWithView; }; /** - * @classdesc - * Base class for tiles. + * Set the rotation. * - * @constructor - * @extends {ol.events.EventTarget} - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.TileState} state State. + * @param {number} rotation Rotation. + * @api */ -ol.Tile = function(tileCoord, state) { - - goog.base(this); - - /** - * @type {ol.TileCoord} - */ - this.tileCoord = tileCoord; - - /** - * @protected - * @type {ol.TileState} - */ - this.state = state; - - /** - * An "interim" tile for this tile. The interim tile may be used while this - * one is loading, for "smooth" transitions when changing params/dimensions - * on the source. - * @type {ol.Tile} - */ - this.interimTile = null; - - /** - * A key assigned to the tile. This is used by the tile source to determine - * if this tile can effectively be used, or if a new tile should be created - * and this one be used as an interim tile for this new tile. - * @type {string} - */ - this.key = ''; - +ol.style.Image.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; }; -goog.inherits(ol.Tile, ol.events.EventTarget); /** - * @protected + * Set the scale. + * + * @param {number} scale Scale. + * @api */ -ol.Tile.prototype.changed = function() { - this.dispatchEvent(ol.events.EventType.CHANGE); +ol.style.Image.prototype.setScale = function(scale) { + this.scale_ = scale; }; /** - * Get the HTML image element for this tile (may be a Canvas, Image, or Video). - * @function - * @param {Object=} opt_context Object. - * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. - */ -ol.Tile.prototype.getImage = goog.abstractMethod; - - -/** - * @return {string} Key. + * Set whether to snap the image to the closest pixel. + * + * @param {boolean} snapToPixel Snap to pixel? */ -ol.Tile.prototype.getKey = function() { - return goog.getUid(this).toString(); +ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) { + this.snapToPixel_ = snapToPixel; }; /** - * Get the tile coordinate for this tile. - * @return {ol.TileCoord} The tile coordinate. - * @api + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {ol.EventsKey|undefined} Listener key. + * @template T */ -ol.Tile.prototype.getTileCoord = function() { - return this.tileCoord; -}; +ol.style.Image.prototype.listenImageChange = goog.abstractMethod; /** - * @return {ol.TileState} State. + * Load not yet loaded URI. */ -ol.Tile.prototype.getState = function() { - return this.state; -}; +ol.style.Image.prototype.load = goog.abstractMethod; /** - * FIXME empty description for jsdoc + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @template T */ -ol.Tile.prototype.load = goog.abstractMethod; - -goog.provide('ol.size'); +ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod; +goog.provide('ol.style.Icon'); +goog.provide('ol.style.IconAnchorUnits'); +goog.provide('ol.style.IconImageCache'); +goog.provide('ol.style.IconOrigin'); goog.require('goog.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); +goog.require('ol.color'); +goog.require('ol.dom'); +goog.require('ol.style.Image'); +goog.require('ol.style.ImageState'); /** - * Returns a buffered size. - * @param {ol.Size} size Size. - * @param {number} buffer Buffer. - * @param {ol.Size=} opt_size Optional reusable size array. - * @return {ol.Size} The buffered size. + * Icon anchor units. One of 'fraction', 'pixels'. + * @enum {string} */ -ol.size.buffer = function(size, buffer, opt_size) { - if (opt_size === undefined) { - opt_size = [0, 0]; - } - opt_size[0] = size[0] + 2 * buffer; - opt_size[1] = size[1] + 2 * buffer; - return opt_size; +ol.style.IconAnchorUnits = { + FRACTION: 'fraction', + PIXELS: 'pixels' }; /** - * Compares sizes for equality. - * @param {ol.Size} a Size. - * @param {ol.Size} b Size. - * @return {boolean} Equals. + * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'. + * @enum {string} */ -ol.size.equals = function(a, b) { - return a[0] == b[0] && a[1] == b[1]; +ol.style.IconOrigin = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' }; /** - * Determines if a size has a positive area. - * @param {ol.Size} size The size to test. - * @return {boolean} The size has a positive area. + * @classdesc + * Set icon style for vector features. + * + * @constructor + * @param {olx.style.IconOptions=} opt_options Options. + * @extends {ol.style.Image} + * @api */ -ol.size.hasArea = function(size) { - return size[0] > 0 && size[1] > 0; -}; +ol.style.Icon = function(opt_options) { + var options = opt_options || {}; -/** - * Returns a size scaled by a ratio. The result will be an array of integers. - * @param {ol.Size} size Size. - * @param {number} ratio Ratio. - * @param {ol.Size=} opt_size Optional reusable size array. - * @return {ol.Size} The scaled size. - */ -ol.size.scale = function(size, ratio, opt_size) { - if (opt_size === undefined) { - opt_size = [0, 0]; - } - opt_size[0] = (size[0] * ratio + 0.5) | 0; - opt_size[1] = (size[1] * ratio + 0.5) | 0; - return opt_size; -}; + /** + * @private + * @type {Array.} + */ + this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5]; + /** + * @private + * @type {Array.} + */ + this.normalizedAnchor_ = null; -/** - * Returns an `ol.Size` array for the passed in number (meaning: square) or - * `ol.Size` array. - * (meaning: non-square), - * @param {number|ol.Size} size Width and height. - * @param {ol.Size=} opt_size Optional reusable size array. - * @return {ol.Size} Size. - * @api stable - */ -ol.size.toSize = function(size, opt_size) { - if (Array.isArray(size)) { - return size; - } else { - goog.asserts.assert(goog.isNumber(size)); - if (opt_size === undefined) { - opt_size = [size, size]; - } else { - opt_size[0] = size; - opt_size[1] = size; - } - return opt_size; - } -}; + /** + * @private + * @type {ol.style.IconOrigin} + */ + this.anchorOrigin_ = options.anchorOrigin !== undefined ? + options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT; -goog.provide('ol.source.Source'); -goog.provide('ol.source.State'); + /** + * @private + * @type {ol.style.IconAnchorUnits} + */ + this.anchorXUnits_ = options.anchorXUnits !== undefined ? + options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION; -goog.require('ol'); -goog.require('ol.Attribution'); -goog.require('ol.Object'); -goog.require('ol.proj'); + /** + * @private + * @type {ol.style.IconAnchorUnits} + */ + this.anchorYUnits_ = options.anchorYUnits !== undefined ? + options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION; + /** + * @type {?string} + */ + var crossOrigin = + options.crossOrigin !== undefined ? options.crossOrigin : null; -/** - * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. - * @enum {string} - * @api - */ -ol.source.State = { - UNDEFINED: 'undefined', - LOADING: 'loading', - READY: 'ready', - ERROR: 'error' -}; + /** + * @type {Image|HTMLCanvasElement} + */ + var image = options.img !== undefined ? options.img : null; + /** + * @type {ol.Size} + */ + var imgSize = options.imgSize !== undefined ? options.imgSize : null; -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for {@link ol.layer.Layer} sources. - * - * A generic `change` event is triggered when the state of the source changes. - * - * @constructor - * @extends {ol.Object} - * @param {ol.SourceSourceOptions} options Source options. - * @api stable - */ -ol.source.Source = function(options) { + /** + * @type {string|undefined} + */ + var src = options.src; + + goog.asserts.assert(!(src !== undefined && image), + 'image and src can not provided at the same time'); + goog.asserts.assert( + !image || (image && imgSize), + 'imgSize must be set when image is provided'); + + if ((src === undefined || src.length === 0) && image) { + src = image.src || goog.getUid(image).toString(); + } + goog.asserts.assert(src !== undefined && src.length > 0, + 'must provide a defined and non-empty src or image'); + + /** + * @type {ol.style.ImageState} + */ + var imageState = options.src !== undefined ? + ol.style.ImageState.IDLE : ol.style.ImageState.LOADED; - goog.base(this); + /** + * @type {ol.Color} + */ + var color = options.color !== undefined ? ol.color.asArray(options.color) : + null; /** * @private - * @type {ol.proj.Projection} + * @type {ol.style.IconImage_} */ - this.projection_ = ol.proj.get(options.projection); + this.iconImage_ = ol.style.IconImage_.get( + image, src, imgSize, crossOrigin, imageState, color); /** * @private - * @type {Array.} + * @type {Array.} */ - this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions); + this.offset_ = options.offset !== undefined ? options.offset : [0, 0]; /** * @private - * @type {string|olx.LogoOptions|undefined} + * @type {ol.style.IconOrigin} */ - this.logo_ = options.logo; + this.offsetOrigin_ = options.offsetOrigin !== undefined ? + options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT; /** * @private - * @type {ol.source.State} + * @type {Array.} */ - this.state_ = options.state !== undefined ? - options.state : ol.source.State.READY; + this.origin_ = null; /** * @private + * @type {ol.Size} + */ + this.size_ = options.size !== undefined ? options.size : null; + + /** + * @type {number} + */ + var opacity = options.opacity !== undefined ? options.opacity : 1; + + /** * @type {boolean} */ - this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false; + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + + /** + * @type {number} + */ + var rotation = options.rotation !== undefined ? options.rotation : 0; + + /** + * @type {number} + */ + var scale = options.scale !== undefined ? options.scale : 1; + + /** + * @type {boolean} + */ + var snapToPixel = options.snapToPixel !== undefined ? + options.snapToPixel : true; + + ol.style.Image.call(this, { + opacity: opacity, + rotation: rotation, + scale: scale, + snapToPixel: snapToPixel, + rotateWithView: rotateWithView + }); }; -goog.inherits(ol.source.Source, ol.Object); +ol.inherits(ol.style.Icon, ol.style.Image); + /** - * Turns various ways of defining an attribution to an array of `ol.Attributions`. - * - * @param {ol.AttributionLike|undefined} - * attributionLike The attributions as string, array of strings, - * `ol.Attribution`, array of `ol.Attribution` or undefined. - * @return {Array.} The array of `ol.Attribution` or null if - * `undefined` was given. + * @inheritDoc + * @api */ -ol.source.Source.toAttributionsArray_ = function(attributionLike) { - if (typeof attributionLike === 'string') { - return [new ol.Attribution({html: attributionLike})]; - } else if (attributionLike instanceof ol.Attribution) { - return [attributionLike]; - } else if (Array.isArray(attributionLike)) { - var len = attributionLike.length; - var attributions = new Array(len); - for (var i = 0; i < len; i++) { - var item = attributionLike[i]; - if (typeof item === 'string') { - attributions[i] = new ol.Attribution({html: item}); - } else { - attributions[i] = item; - } +ol.style.Icon.prototype.getAnchor = function() { + if (this.normalizedAnchor_) { + return this.normalizedAnchor_; + } + var anchor = this.anchor_; + var size = this.getSize(); + if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION || + this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { + if (!size) { + return null; + } + anchor = this.anchor_.slice(); + if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) { + anchor[0] *= size[0]; + } + if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { + anchor[1] *= size[1]; } - return attributions; - } else { - return null; } -} - -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {Object.} skippedFeatureUids Skipped feature uids. - * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature - * callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction; + if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) { + if (!size) { + return null; + } + if (anchor === this.anchor_) { + anchor = this.anchor_.slice(); + } + if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + anchor[0] = -anchor[0] + size[0]; + } + if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + anchor[1] = -anchor[1] + size[1]; + } + } + this.normalizedAnchor_ = anchor; + return this.normalizedAnchor_; +}; /** - * Get the attributions of the source. - * @return {Array.} Attributions. - * @api stable + * Get the image icon. + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. + * @api */ -ol.source.Source.prototype.getAttributions = function() { - return this.attributions_; +ol.style.Icon.prototype.getImage = function(pixelRatio) { + return this.iconImage_.getImage(pixelRatio); }; /** - * Get the logo of the source. - * @return {string|olx.LogoOptions|undefined} Logo. - * @api stable + * Real Image size used. + * @return {ol.Size} Size. */ -ol.source.Source.prototype.getLogo = function() { - return this.logo_; +ol.style.Icon.prototype.getImageSize = function() { + return this.iconImage_.getSize(); }; /** - * Get the projection of the source. - * @return {ol.proj.Projection} Projection. - * @api + * @inheritDoc */ -ol.source.Source.prototype.getProjection = function() { - return this.projection_; +ol.style.Icon.prototype.getHitDetectionImageSize = function() { + return this.getImageSize(); }; /** - * @return {Array.|undefined} Resolutions. + * @inheritDoc */ -ol.source.Source.prototype.getResolutions = goog.abstractMethod; +ol.style.Icon.prototype.getImageState = function() { + return this.iconImage_.getImageState(); +}; /** - * Get the state of the source, see {@link ol.source.State} for possible states. - * @return {ol.source.State} State. - * @api + * @inheritDoc */ -ol.source.Source.prototype.getState = function() { - return this.state_; +ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { + return this.iconImage_.getHitDetectionImage(pixelRatio); }; /** - * @return {boolean|undefined} Wrap X. - */ -ol.source.Source.prototype.getWrapX = function() { - return this.wrapX_; + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getOrigin = function() { + if (this.origin_) { + return this.origin_; + } + var offset = this.offset_; + + if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) { + var size = this.getSize(); + var iconImageSize = this.iconImage_.getSize(); + if (!size || !iconImageSize) { + return null; + } + offset = offset.slice(); + if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[0] = iconImageSize[0] - size[0] - offset[0]; + } + if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[1] = iconImageSize[1] - size[1] - offset[1]; + } + } + this.origin_ = offset; + return this.origin_; }; /** - * Refreshes the source and finally dispatches a 'change' event. + * Get the image URL. + * @return {string|undefined} Image src. * @api */ -ol.source.Source.prototype.refresh = function() { - this.changed(); +ol.style.Icon.prototype.getSrc = function() { + return this.iconImage_.getSrc(); }; /** - * Set the attributions of the source. - * @param {ol.AttributionLike|undefined} attributions Attributions. - * Can be passed as `string`, `Array`, `{@link ol.Attribution}`, - * `Array<{@link ol.Attribution}>` or `undefined`. + * @inheritDoc * @api */ -ol.source.Source.prototype.setAttributions = function(attributions) { - this.attributions_ = ol.source.Source.toAttributionsArray_(attributions); - this.changed(); +ol.style.Icon.prototype.getSize = function() { + return !this.size_ ? this.iconImage_.getSize() : this.size_; }; /** - * Set the logo of the source. - * @param {string|olx.LogoOptions|undefined} logo Logo. + * @inheritDoc */ -ol.source.Source.prototype.setLogo = function(logo) { - this.logo_ = logo; +ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) { + return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); }; /** - * Set the state of the source. - * @param {ol.source.State} state State. - * @protected + * Load not yet loaded URI. + * When rendering a feature with an icon style, the vector renderer will + * automatically call this method. However, you might want to call this + * method yourself for preloading or other purposes. + * @api */ -ol.source.Source.prototype.setState = function(state) { - this.state_ = state; - this.changed(); +ol.style.Icon.prototype.load = function() { + this.iconImage_.load(); }; -goog.provide('ol.tilegrid.TileGrid'); -goog.require('goog.asserts'); -goog.require('ol'); -goog.require('ol.TileRange'); -goog.require('ol.array'); -goog.require('ol.extent'); -goog.require('ol.extent.Corner'); -goog.require('ol.math'); -goog.require('ol.object'); -goog.require('ol.proj'); -goog.require('ol.proj.METERS_PER_UNIT'); -goog.require('ol.proj.Projection'); -goog.require('ol.proj.Units'); -goog.require('ol.size'); -goog.require('ol.tilecoord'); +/** + * @inheritDoc + */ +ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) { + ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); +}; /** - * @classdesc - * Base class for setting the grid pattern for sources accessing tiled-image - * servers. - * * @constructor - * @param {olx.tilegrid.TileGridOptions} options Tile grid options. - * @struct - * @api stable + * @param {Image|HTMLCanvasElement} image Image. + * @param {string|undefined} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.style.ImageState} imageState Image state. + * @param {ol.Color} color Color. + * @extends {ol.events.EventTarget} + * @private */ -ol.tilegrid.TileGrid = function(options) { +ol.style.IconImage_ = function(image, src, size, crossOrigin, imageState, + color) { - /** - * @protected - * @type {number} - */ - this.minZoom = options.minZoom !== undefined ? options.minZoom : 0; + ol.events.EventTarget.call(this); /** * @private - * @type {!Array.} + * @type {Image|HTMLCanvasElement} */ - this.resolutions_ = options.resolutions; - goog.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) { - return b - a; - }, true), 'resolutions must be sorted in descending order'); + this.hitDetectionImage_ = null; /** - * @protected - * @type {number} + * @private + * @type {Image|HTMLCanvasElement} */ - this.maxZoom = this.resolutions_.length - 1; + this.image_ = !image ? new Image() : image; + + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } /** * @private - * @type {ol.Coordinate} + * @type {HTMLCanvasElement} */ - this.origin_ = options.origin !== undefined ? options.origin : null; + this.canvas_ = color ? + /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) : + null; /** * @private - * @type {Array.} + * @type {ol.Color} */ - this.origins_ = null; - if (options.origins !== undefined) { - this.origins_ = options.origins; - goog.asserts.assert(this.origins_.length == this.resolutions_.length, - 'number of origins and resolutions must be equal'); - } - - var extent = options.extent; - - if (extent !== undefined && - !this.origin_ && !this.origins_) { - this.origin_ = ol.extent.getTopLeft(extent); - } - - goog.asserts.assert( - (!this.origin_ && this.origins_) || - (this.origin_ && !this.origins_), - 'either origin or origins must be configured, never both'); + this.color_ = color; /** * @private - * @type {Array.} + * @type {Array.} */ - this.tileSizes_ = null; - if (options.tileSizes !== undefined) { - this.tileSizes_ = options.tileSizes; - goog.asserts.assert(this.tileSizes_.length == this.resolutions_.length, - 'number of tileSizes and resolutions must be equal'); - } + this.imageListenerKeys_ = null; /** * @private - * @type {number|ol.Size} + * @type {ol.style.ImageState} */ - this.tileSize_ = options.tileSize !== undefined ? - options.tileSize : - !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null; - goog.asserts.assert( - (!this.tileSize_ && this.tileSizes_) || - (this.tileSize_ && !this.tileSizes_), - 'either tileSize or tileSizes must be configured, never both'); + this.imageState_ = imageState; /** * @private - * @type {ol.Extent} + * @type {ol.Size} */ - this.extent_ = extent !== undefined ? extent : null; - + this.size_ = size; /** * @private - * @type {Array.} + * @type {string|undefined} */ - this.fullTileRanges_ = null; + this.src_ = src; /** * @private - * @type {ol.Size} + * @type {boolean} */ - this.tmpSize_ = [0, 0]; - - if (options.sizes !== undefined) { - goog.asserts.assert(options.sizes.length == this.resolutions_.length, - 'number of sizes and resolutions must be equal'); - this.fullTileRanges_ = options.sizes.map(function(size, z) { - goog.asserts.assert(size[0] !== 0, 'width must not be 0'); - goog.asserts.assert(size[1] !== 0, 'height must not be 0'); - var tileRange = new ol.TileRange( - Math.min(0, size[0]), Math.max(size[0] - 1, -1), - Math.min(0, size[1]), Math.max(size[1] - 1, -1)); - if (this.minZoom <= z && z <= this.maxZoom && extent !== undefined) { - goog.asserts.assert(tileRange.containsTileRange( - this.getTileRangeForExtentAndZ(extent, z)), - 'extent tile range must not exceed tilegrid width and height'); - } - return tileRange; - }, this); - } else if (extent) { - this.calculateTileRanges_(extent); + this.tainting_ = false; + if (this.imageState_ == ol.style.ImageState.LOADED) { + this.determineTainting_(); } }; +ol.inherits(ol.style.IconImage_, ol.events.EventTarget); /** - * @private - * @type {ol.TileCoord} - */ -ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; - - -/** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. - * @param {T=} opt_this The object to use as `this` in `callback`. - * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {boolean} Callback succeeded. - * @template T + * @param {Image|HTMLCanvasElement} image Image. + * @param {string} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.style.ImageState} imageState Image state. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage_} Icon image. */ -ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { - var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); - var z = tileCoord[0] - 1; - while (z >= this.minZoom) { - if (callback.call(opt_this, z, - this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { - return true; - } - --z; +ol.style.IconImage_.get = function(image, src, size, crossOrigin, imageState, + color) { + var iconImageCache = ol.style.IconImageCache.getInstance(); + var iconImage = iconImageCache.get(src, crossOrigin, color); + if (!iconImage) { + iconImage = new ol.style.IconImage_( + image, src, size, crossOrigin, imageState, color); + iconImageCache.set(src, crossOrigin, color, iconImage); } - return false; + return iconImage; }; /** - * Get the extent for this tile grid, if it was configured. - * @return {ol.Extent} Extent. + * @private */ -ol.tilegrid.TileGrid.prototype.getExtent = function() { - return this.extent_; +ol.style.IconImage_.prototype.determineTainting_ = function() { + var context = ol.dom.createCanvasContext2D(1, 1); + try { + context.drawImage(this.image_, 0, 0); + context.getImageData(0, 0, 1, 1); + } catch (e) { + this.tainting_ = true; + } }; /** - * Get the maximum zoom level for the grid. - * @return {number} Max zoom. - * @api + * @private */ -ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { - return this.maxZoom; +ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); }; /** - * Get the minimum zoom level for the grid. - * @return {number} Min zoom. - * @api + * @private */ -ol.tilegrid.TileGrid.prototype.getMinZoom = function() { - return this.minZoom; +ol.style.IconImage_.prototype.handleImageError_ = function() { + this.imageState_ = ol.style.ImageState.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent_(); }; /** - * Get the origin for the grid at the given zoom level. - * @param {number} z Z. - * @return {ol.Coordinate} Origin. - * @api stable + * @private */ -ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { - if (this.origin_) { - return this.origin_; - } else { - goog.asserts.assert(this.origins_, - 'origins cannot be null if origin is null'); - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, - 'given z is not in allowed range (%s <= %s <= %s)', - this.minZoom, z, this.maxZoom); - return this.origins_[z]; +ol.style.IconImage_.prototype.handleImageLoad_ = function() { + this.imageState_ = ol.style.ImageState.LOADED; + if (this.size_) { + this.image_.width = this.size_[0]; + this.image_.height = this.size_[1]; } + this.size_ = [this.image_.width, this.image_.height]; + this.unlistenImage_(); + this.determineTainting_(); + this.replaceColor_(); + this.dispatchChangeEvent_(); }; /** - * Get the resolution for the given zoom level. - * @param {number} z Z. - * @return {number} Resolution. - * @api stable + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. */ -ol.tilegrid.TileGrid.prototype.getResolution = function(z) { - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, - 'given z is not in allowed range (%s <= %s <= %s)', - this.minZoom, z, this.maxZoom); - return this.resolutions_[z]; +ol.style.IconImage_.prototype.getImage = function(pixelRatio) { + return this.canvas_ ? this.canvas_ : this.image_; }; /** - * Get the list of resolutions for the tile grid. - * @return {Array.} Resolutions. - * @api stable + * @return {ol.style.ImageState} Image state. */ -ol.tilegrid.TileGrid.prototype.getResolutions = function() { - return this.resolutions_; +ol.style.IconImage_.prototype.getImageState = function() { + return this.imageState_; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {ol.TileRange} Tile range. + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image element. */ -ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) { - if (tileCoord[0] < this.maxZoom) { - var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); - return this.getTileRangeForExtentAndZ( - tileCoordExtent, tileCoord[0] + 1, opt_tileRange); - } else { - return null; +ol.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) { + if (!this.hitDetectionImage_) { + if (this.tainting_) { + var width = this.size_[0]; + var height = this.size_[1]; + var context = ol.dom.createCanvasContext2D(width, height); + context.fillRect(0, 0, width, height); + this.hitDetectionImage_ = context.canvas; + } else { + this.hitDetectionImage_ = this.image_; + } } + return this.hitDetectionImage_; }; /** - * @param {number} z Z. - * @param {ol.TileRange} tileRange Tile range. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {ol.Extent} Extent. + * @return {ol.Size} Image size. */ -ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) { - var origin = this.getOrigin(z); - var resolution = this.getResolution(z); - var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); - var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; - var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; - var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; - var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +ol.style.IconImage_.prototype.getSize = function() { + return this.size_; }; /** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {ol.TileRange=} opt_tileRange Temporary tile range object. - * @return {ol.TileRange} Tile range. + * @return {string|undefined} Image src. */ -ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) { - var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; - this.getTileCoordForXYAndResolution_( - extent[0], extent[1], resolution, false, tileCoord); - var minX = tileCoord[1]; - var minY = tileCoord[2]; - this.getTileCoordForXYAndResolution_( - extent[2], extent[3], resolution, true, tileCoord); - return ol.TileRange.createOrUpdate( - minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +ol.style.IconImage_.prototype.getSrc = function() { + return this.src_; }; /** - * @param {ol.Extent} extent Extent. - * @param {number} z Z. - * @param {ol.TileRange=} opt_tileRange Temporary tile range object. - * @return {ol.TileRange} Tile range. + * Load not yet loaded URI. */ -ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) { - var resolution = this.getResolution(z); - return this.getTileRangeForExtentAndResolution( - extent, resolution, opt_tileRange); +ol.style.IconImage_.prototype.load = function() { + if (this.imageState_ == ol.style.ImageState.IDLE) { + goog.asserts.assert(this.src_ !== undefined, + 'this.src_ must not be undefined'); + goog.asserts.assert(!this.imageListenerKeys_, + 'no listener keys existing'); + this.imageState_ = ol.style.ImageState.LOADING; + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + try { + this.image_.src = this.src_; + } catch (e) { + this.handleImageError_(); + } + } }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {ol.Coordinate} Tile center. + * @private */ -ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { - var origin = this.getOrigin(tileCoord[0]); - var resolution = this.getResolution(tileCoord[0]); - var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); - return [ - origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, - origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution - ]; -}; +ol.style.IconImage_.prototype.replaceColor_ = function() { + if (this.tainting_ || this.color_ === null) { + return; + } + goog.asserts.assert(this.canvas_ !== null, + 'this.canvas_ must not be null'); -/** - * Get the extent of a tile coordinate. - * - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.Extent=} opt_extent Temporary extent object. - * @return {ol.Extent} Extent. - * @api - */ -ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) { - var origin = this.getOrigin(tileCoord[0]); - var resolution = this.getResolution(tileCoord[0]); - var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); - var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; - var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; - var maxX = minX + tileSize[0] * resolution; - var maxY = minY + tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); + this.canvas_.width = this.image_.width; + this.canvas_.height = this.image_.height; + + var ctx = this.canvas_.getContext('2d'); + ctx.drawImage(this.image_, 0, 0); + + var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height); + var data = imgData.data; + var r = this.color_[0] / 255.0; + var g = this.color_[1] / 255.0; + var b = this.color_[2] / 255.0; + + for (var i = 0, ii = data.length; i < ii; i += 4) { + data[i] *= r; + data[i + 1] *= g; + data[i + 2] *= b; + } + ctx.putImageData(imgData, 0, 0); }; /** - * Get the tile coordinate for the given map coordinate and resolution. This - * method considers that coordinates that intersect tile boundaries should be - * assigned the higher tile coordinate. + * Discards event handlers which listen for load completion or errors. * - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} resolution Resolution. - * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. - * @api + * @private */ -ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) { - return this.getTileCoordForXYAndResolution_( - coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +ol.style.IconImage_.prototype.unlistenImage_ = function() { + goog.asserts.assert(this.imageListenerKeys_, + 'we must have listeners registered'); + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; }; /** - * @param {number} x X. - * @param {number} y Y. - * @param {number} resolution Resolution. - * @param {boolean} reverseIntersectionPolicy Instead of letting edge - * intersections go to the higher tile coordinate, let edge intersections - * go to the lower tile coordinate. - * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. - * @private + * @constructor */ -ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( - x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { - var z = this.getZForResolution(resolution); - var scale = resolution / this.getResolution(z); - var origin = this.getOrigin(z); - var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); +ol.style.IconImageCache = function() { - var adjustX = reverseIntersectionPolicy ? 0.5 : 0; - var adjustY = reverseIntersectionPolicy ? 0 : 0.5; - var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); - var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY); - var tileCoordX = scale * xFromOrigin / tileSize[0]; - var tileCoordY = scale * yFromOrigin / tileSize[1]; + /** + * @type {Object.} + * @private + */ + this.cache_ = {}; - if (reverseIntersectionPolicy) { - tileCoordX = Math.ceil(tileCoordX) - 1; - tileCoordY = Math.ceil(tileCoordY) - 1; - } else { - tileCoordX = Math.floor(tileCoordX); - tileCoordY = Math.floor(tileCoordY); - } + /** + * @type {number} + * @private + */ + this.cacheSize_ = 0; - return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); + /** + * @const + * @type {number} + * @private + */ + this.maxCacheSize_ = 32; }; +goog.addSingletonGetter(ol.style.IconImageCache); /** - * Get a tile coordinate given a map coordinate and zoom level. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} z Zoom level. - * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. - * @api + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {string} Cache key. */ -ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) { - var resolution = this.getResolution(z); - return this.getTileCoordForXYAndResolution_( - coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +ol.style.IconImageCache.getKey = function(src, crossOrigin, color) { + goog.asserts.assert(crossOrigin !== undefined, + 'argument crossOrigin must be defined'); + var colorString = color ? ol.color.asString(color) : 'null'; + return crossOrigin + ':' + src + ':' + colorString; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {number} Tile resolution. + * FIXME empty description for jsdoc */ -ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { - goog.asserts.assert( - this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom, - 'z of given tilecoord is not in allowed range (%s <= %s <= %s', - this.minZoom, tileCoord[0], this.maxZoom); - return this.resolutions_[tileCoord[0]]; +ol.style.IconImageCache.prototype.clear = function() { + this.cache_ = {}; + this.cacheSize_ = 0; }; /** - * Get the tile size for a zoom level. The type of the return value matches the - * `tileSize` or `tileSizes` that the tile grid was configured with. To always - * get an `ol.Size`, run the result through `ol.size.toSize()`. - * @param {number} z Z. - * @return {number|ol.Size} Tile size. - * @api stable + * FIXME empty description for jsdoc */ -ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { - if (this.tileSize_) { - return this.tileSize_; - } else { - goog.asserts.assert(this.tileSizes_, - 'tileSizes cannot be null if tileSize is null'); - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, - 'z is not in allowed range (%s <= %s <= %s', - this.minZoom, z, this.maxZoom); - return this.tileSizes_[z]; +ol.style.IconImageCache.prototype.expire = function() { + if (this.cacheSize_ > this.maxCacheSize_) { + var i = 0; + var key, iconImage; + for (key in this.cache_) { + iconImage = this.cache_[key]; + if ((i++ & 3) === 0 && !iconImage.hasListener()) { + delete this.cache_[key]; + --this.cacheSize_; + } + } } }; /** - * @param {number} z Zoom level. - * @return {ol.TileRange} Extent tile range for the specified zoom level. + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage_} Icon image. */ -ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { - if (!this.fullTileRanges_) { - return null; - } else { - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, - 'z is not in allowed range (%s <= %s <= %s', - this.minZoom, z, this.maxZoom); - return this.fullTileRanges_[z]; - } +ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + return key in this.cache_ ? this.cache_[key] : null; }; /** - * @param {number} resolution Resolution. - * @param {number=} opt_direction If 0, the nearest resolution will be used. - * If 1, the nearest lower resolution will be used. If -1, the nearest - * higher resolution will be used. Default is 0. - * @return {number} Z. + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @param {ol.style.IconImage_} iconImage Icon image. */ -ol.tilegrid.TileGrid.prototype.getZForResolution = function( - resolution, opt_direction) { - var z = ol.array.linearFindNearest(this.resolutions_, resolution, - opt_direction || 0); - return ol.math.clamp(z, this.minZoom, this.maxZoom); +ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color, + iconImage) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + this.cache_[key] = iconImage; + ++this.cacheSize_; }; +goog.provide('ol.RendererType'); +goog.provide('ol.renderer.Map'); -/** - * @param {!ol.Extent} extent Extent for this tile grid. - * @private - */ -ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) { - var length = this.resolutions_.length; - var fullTileRanges = new Array(length); - for (var z = this.minZoom; z < length; ++z) { - fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z); - } - this.fullTileRanges_ = fullTileRanges; -}; +goog.require('goog.asserts'); +goog.require('goog.vec.Mat4'); +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.layer.Layer'); +goog.require('ol.renderer.Layer'); +goog.require('ol.style.IconImageCache'); +goog.require('ol.vec.Mat4'); /** - * @param {ol.proj.Projection} projection Projection. - * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection. + * Available renderers: `'canvas'`, `'dom'` or `'webgl'`. + * @enum {string} */ -ol.tilegrid.getForProjection = function(projection) { - var tileGrid = projection.getDefaultTileGrid(); - if (!tileGrid) { - tileGrid = ol.tilegrid.createForProjection(projection); - projection.setDefaultTileGrid(tileGrid); - } - return tileGrid; +ol.RendererType = { + CANVAS: 'canvas', + DOM: 'dom', + WEBGL: 'webgl' }; /** - * @param {ol.Extent} extent Extent. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {number|ol.Size=} opt_tileSize Tile size (default uses - * ol.DEFAULT_TILE_SIZE). - * @param {ol.extent.Corner=} opt_corner Extent corner (default is - * ol.extent.Corner.TOP_LEFT). - * @return {ol.tilegrid.TileGrid} TileGrid instance. + * @constructor + * @extends {ol.Disposable} + * @param {Element} container Container. + * @param {ol.Map} map Map. + * @struct */ -ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { - var corner = opt_corner !== undefined ? - opt_corner : ol.extent.Corner.TOP_LEFT; +ol.renderer.Map = function(container, map) { - var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, opt_maxZoom, opt_tileSize); + ol.Disposable.call(this); - return new ol.tilegrid.TileGrid({ - extent: extent, - origin: ol.extent.getCorner(extent, corner), - resolutions: resolutions, - tileSize: opt_tileSize - }); -}; + /** + * @private + * @type {ol.Map} + */ + this.map_ = map; -/** - * Creates a tile grid with a standard XYZ tiling scheme. - * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. - * @return {ol.tilegrid.TileGrid} Tile grid instance. - * @api - */ -ol.tilegrid.createXYZ = function(opt_options) { - var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); - ol.object.assign(options, opt_options !== undefined ? - opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); - if (options.extent === undefined) { - options.extent = ol.proj.get('EPSG:3857').getExtent(); - } - options.resolutions = ol.tilegrid.resolutionsFromExtent( - options.extent, options.maxZoom, options.tileSize); - delete options.maxZoom; + /** + * @private + * @type {Object.} + */ + this.layerRenderers_ = {}; + + /** + * @private + * @type {Object.} + */ + this.layerRendererListeners_ = {}; - return new ol.tilegrid.TileGrid(options); }; +ol.inherits(ol.renderer.Map, ol.Disposable); /** - * Create a resolutions array from an extent. A zoom factor of 2 is assumed. - * @param {ol.Extent} extent Extent. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {number|ol.Size=} opt_tileSize Tile size (default uses - * ol.DEFAULT_TILE_SIZE). - * @return {!Array.} Resolutions array. + * @param {olx.FrameState} frameState FrameState. + * @protected */ -ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) { - var maxZoom = opt_maxZoom !== undefined ? - opt_maxZoom : ol.DEFAULT_MAX_ZOOM; - - var height = ol.extent.getHeight(extent); - var width = ol.extent.getWidth(extent); - - var tileSize = ol.size.toSize(opt_tileSize !== undefined ? - opt_tileSize : ol.DEFAULT_TILE_SIZE); - var maxResolution = Math.max( - width / tileSize[0], height / tileSize[1]); - - var length = maxZoom + 1; - var resolutions = new Array(length); - for (var z = 0; z < length; ++z) { - resolutions[z] = maxResolution / Math.pow(2, z); - } - return resolutions; +ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { + var viewState = frameState.viewState; + var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix; + goog.asserts.assert(coordinateToPixelMatrix, + 'frameState has a coordinateToPixelMatrix'); + ol.vec.Mat4.makeTransform2D(coordinateToPixelMatrix, + frameState.size[0] / 2, frameState.size[1] / 2, + 1 / viewState.resolution, -1 / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + var inverted = goog.vec.Mat4.invert( + coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix); + goog.asserts.assert(inverted, 'matrix could be inverted'); }; /** - * @param {ol.proj.ProjectionLike} projection Projection. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). - * @param {ol.extent.Corner=} opt_corner Extent corner (default is - * ol.extent.Corner.BOTTOM_LEFT). - * @return {ol.tilegrid.TileGrid} TileGrid instance. + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} layerRenderer Layer renderer. */ -ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) { - var extent = ol.tilegrid.extentFromProjection(projection); - return ol.tilegrid.createForExtent( - extent, opt_maxZoom, opt_tileSize, opt_corner); -}; +ol.renderer.Map.prototype.createLayerRenderer = goog.abstractMethod; /** - * Generate a tile grid extent from a projection. If the projection has an - * extent, it is used. If not, a global extent is assumed. - * @param {ol.proj.ProjectionLike} projection Projection. - * @return {ol.Extent} Extent. + * @inheritDoc */ -ol.tilegrid.extentFromProjection = function(projection) { - projection = ol.proj.get(projection); - var extent = projection.getExtent(); - if (!extent) { - var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / - projection.getMetersPerUnit(); - extent = ol.extent.createOrUpdate(-half, -half, half, half); +ol.renderer.Map.prototype.disposeInternal = function() { + for (var id in this.layerRenderers_) { + this.layerRenderers_[id].dispose(); } - return extent; }; -goog.provide('ol.source.Tile'); -goog.provide('ol.source.TileEvent'); - -goog.require('goog.asserts'); -goog.require('ol.events.Event'); -goog.require('ol'); -goog.require('ol.TileCache'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); -goog.require('ol.proj'); -goog.require('ol.size'); -goog.require('ol.source.Source'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); - /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for sources providing images divided into a tile grid. - * - * @constructor - * @extends {ol.source.Source} - * @param {ol.SourceTileOptions} options Tile source options. - * @api + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private */ -ol.source.Tile = function(options) { - - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - projection: options.projection, - state: options.state, - wrapX: options.wrapX - }); - - /** - * @private - * @type {boolean} - */ - this.opaque_ = options.opaque !== undefined ? options.opaque : false; - - /** - * @private - * @type {number} - */ - this.tilePixelRatio_ = options.tilePixelRatio !== undefined ? - options.tilePixelRatio : 1; - - /** - * @protected - * @type {ol.tilegrid.TileGrid} - */ - this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null; +ol.renderer.Map.expireIconCache_ = function(map, frameState) { + ol.style.IconImageCache.getInstance().expire(); +}; - /** - * @protected - * @type {ol.TileCache} - */ - this.tileCache = new ol.TileCache(options.cacheSize); - /** - * @protected - * @type {ol.Size} - */ - this.tmpSize = [0, 0]; +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; /** - * @private - * @type {string} + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {?} Callback result. */ - this.key_ = ''; - -}; -goog.inherits(ol.source.Tile, ol.source.Source); - + function forEachFeatureAtCoordinate(feature, layer) { + goog.asserts.assert(feature !== undefined, 'received a feature'); + var key = goog.getUid(feature).toString(); + var managed = frameState.layerStates[goog.getUid(layer)].managed; + if (!(key in frameState.skippedFeatureUids && !managed)) { + return callback.call(thisArg, feature, managed ? layer : null); + } + } -/** - * @return {boolean} Can expire cache. - */ -ol.source.Tile.prototype.canExpireCache = function() { - return this.tileCache.canExpireCache(); -}; + var projection = viewState.projection; + var translatedCoordinate = coordinate; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = coordinate[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; + } + } -/** - * @param {ol.proj.Projection} projection Projection. - * @param {Object.} usedTiles Used tiles. - */ -ol.source.Tile.prototype.expireCache = function(projection, usedTiles) { - var tileCache = this.getTileCacheForProjection(projection); - if (tileCache) { - tileCache.expireCache(usedTiles); + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + if (layer.getSource()) { + result = layerRenderer.forEachFeatureAtCoordinate( + layer.getSource().getWrapX() ? translatedCoordinate : coordinate, + frameState, forEachFeatureAtCoordinate, thisArg); + } + if (result) { + return result; + } + } } + return undefined; }; /** - * @param {ol.proj.Projection} projection Projection. - * @param {number} z Zoom level. - * @param {ol.TileRange} tileRange Tile range. - * @param {function(ol.Tile):(boolean|undefined)} callback Called with each - * loaded tile. If the callback returns `false`, the tile will not be - * considered loaded. - * @return {boolean} The tile range is fully covered with loaded tiles. + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U */ -ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) { - var tileCache = this.getTileCacheForProjection(projection); - if (!tileCache) { - return false; - } +ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; - var covered = true; - var tile, tileCoordKey, loaded; - for (var x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (var y = tileRange.minY; y <= tileRange.maxY; ++y) { - tileCoordKey = this.getKeyZXY(z, x, y); - loaded = false; - if (tileCache.containsKey(tileCoordKey)) { - tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey)); - loaded = tile.getState() === ol.TileState.LOADED; - if (loaded) { - loaded = (callback(tile) !== false); - } - } - if (!loaded) { - covered = false; + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; } } } - return covered; + return undefined; }; /** - * @param {ol.proj.Projection} projection Projection. - * @return {number} Gutter. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg Value to use as `this` when executing `layerFilter`. + * @return {boolean} Is there a feature at the given coordinate? + * @template U */ -ol.source.Tile.prototype.getGutter = function(projection) { - return 0; +ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, ol.functions.TRUE, this, layerFilter, thisArg); + + return hasFeature !== undefined; }; /** - * Return the key to be used for all tiles in the source. - * @return {string} The key for all tiles. + * @param {ol.layer.Layer} layer Layer. * @protected + * @return {ol.renderer.Layer} Layer renderer. */ -ol.source.Tile.prototype.getKey = function() { - return this.key_; -}; - +ol.renderer.Map.prototype.getLayerRenderer = function(layer) { + var layerKey = goog.getUid(layer).toString(); + if (layerKey in this.layerRenderers_) { + return this.layerRenderers_[layerKey]; + } else { + var layerRenderer = this.createLayerRenderer(layer); + this.layerRenderers_[layerKey] = layerRenderer; + this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer, + ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this); -/** - * Set the value to be used as the key for all tiles in the source. - * @param {string} key The key for tiles. - * @protected - */ -ol.source.Tile.prototype.setKey = function(key) { - if (this.key_ !== key) { - this.key_ = key; - this.changed(); + return layerRenderer; } }; /** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @return {string} Key. + * @param {string} layerKey Layer key. * @protected + * @return {ol.renderer.Layer} Layer renderer. */ -ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; - - -/** - * @param {ol.proj.Projection} projection Projection. - * @return {boolean} Opaque. - */ -ol.source.Tile.prototype.getOpaque = function(projection) { - return this.opaque_; +ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) { + goog.asserts.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + return this.layerRenderers_[layerKey]; }; /** - * @inheritDoc + * @protected + * @return {Object.} Layer renderers. */ -ol.source.Tile.prototype.getResolutions = function() { - return this.tileGrid.getResolutions(); +ol.renderer.Map.prototype.getLayerRenderers = function() { + return this.layerRenderers_; }; /** - * @param {number} z Tile coordinate z. - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {!ol.Tile} Tile. + * @return {ol.Map} Map. */ -ol.source.Tile.prototype.getTile = goog.abstractMethod; +ol.renderer.Map.prototype.getMap = function() { + return this.map_; +}; /** - * Return the tile grid of the tile source. - * @return {ol.tilegrid.TileGrid} Tile grid. - * @api stable + * @return {string} Type */ -ol.source.Tile.prototype.getTileGrid = function() { - return this.tileGrid; -}; +ol.renderer.Map.prototype.getType = goog.abstractMethod; /** - * @param {ol.proj.Projection} projection Projection. - * @return {ol.tilegrid.TileGrid} Tile grid. + * Handle changes in a layer renderer. + * @private */ -ol.source.Tile.prototype.getTileGridForProjection = function(projection) { - if (!this.tileGrid) { - return ol.tilegrid.getForProjection(projection); - } else { - return this.tileGrid; - } +ol.renderer.Map.prototype.handleLayerRendererChange_ = function() { + this.map_.render(); }; /** - * @param {ol.proj.Projection} projection Projection. - * @return {ol.TileCache} Tile cache. - * @protected + * @param {string} layerKey Layer key. + * @return {ol.renderer.Layer} Layer renderer. + * @private */ -ol.source.Tile.prototype.getTileCacheForProjection = function(projection) { - var thisProj = this.getProjection(); - if (thisProj && !ol.proj.equivalent(thisProj, projection)) { - return null; - } else { - return this.tileCache; - } -}; +ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) { + goog.asserts.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + var layerRenderer = this.layerRenderers_[layerKey]; + delete this.layerRenderers_[layerKey]; + goog.asserts.assert(layerKey in this.layerRendererListeners_, + 'given layerKey (%s) exists in layerRendererListeners', layerKey); + ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]); + delete this.layerRendererListeners_[layerKey]; -/** - * @param {number} pixelRatio Pixel ratio. - * @return {number} Tile pixel ratio. - */ -ol.source.Tile.prototype.getTilePixelRatio = function(pixelRatio) { - return this.tilePixelRatio_; + return layerRenderer; }; /** - * @param {number} z Z. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {ol.Size} Tile size. + * Render. + * @param {?olx.FrameState} frameState Frame state. */ -ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { - var tileGrid = this.getTileGridForProjection(projection); - var tilePixelRatio = this.getTilePixelRatio(pixelRatio); - var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize); - if (tilePixelRatio == 1) { - return tileSize; - } else { - return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize); - } -}; +ol.renderer.Map.prototype.renderFrame = ol.nullFunction; /** - * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate - * is outside the resolution and extent range of the tile grid, `null` will be - * returned. - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection=} opt_projection Projection. - * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or - * null if no tile URL should be created for the passed `tileCoord`. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private */ -ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) { - var projection = opt_projection !== undefined ? - opt_projection : this.getProjection(); - var tileGrid = this.getTileGridForProjection(projection); - goog.asserts.assert(tileGrid, 'tile grid needed'); - if (this.getWrapX() && projection.isGlobal()) { - tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); +ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!frameState || !(layerKey in frameState.layerStates)) { + this.removeLayerRendererByKey_(layerKey).dispose(); + } } - return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null; }; /** - * @inheritDoc + * @param {olx.FrameState} frameState Frame state. + * @protected */ -ol.source.Tile.prototype.refresh = function() { - this.tileCache.clear(); - this.changed(); +ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_) + ); }; /** - * Marks a tile coord as being used, without triggering a load. - * @param {number} z Tile coordinate z. - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. - * @param {ol.proj.Projection} projection Projection. - */ -ol.source.Tile.prototype.useTile = ol.nullFunction; - - -/** - * @classdesc - * Events emitted by {@link ol.source.Tile} instances are instances of this - * type. - * - * @constructor - * @extends {ol.events.Event} - * @implements {oli.source.TileEvent} - * @param {string} type Type. - * @param {ol.Tile} tile The tile. + * @param {!olx.FrameState} frameState Frame state. + * @protected */ -ol.source.TileEvent = function(type, tile) { - - goog.base(this, type); - - /** - * The tile related to the event. - * @type {ol.Tile} - * @api - */ - this.tile = tile; - +ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = function(frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!(layerKey in frameState.layerStates)) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this)) + ); + return; + } + } }; -goog.inherits(ol.source.TileEvent, ol.events.Event); /** - * @enum {string} + * @param {ol.LayerState} state1 First layer state. + * @param {ol.LayerState} state2 Second layer state. + * @return {number} The zIndex difference. */ -ol.source.TileEventType = { - - /** - * Triggered when a tile starts loading. - * @event ol.source.TileEvent#tileloadstart - * @api stable - */ - TILELOADSTART: 'tileloadstart', - - /** - * Triggered when a tile finishes loading. - * @event ol.source.TileEvent#tileloadend - * @api stable - */ - TILELOADEND: 'tileloadend', - - /** - * Triggered if tile loading results in an error. - * @event ol.source.TileEvent#tileloaderror - * @api stable - */ - TILELOADERROR: 'tileloaderror' - +ol.renderer.Map.sortByZIndex = function(state1, state2) { + return state1.zIndex - state2.zIndex; }; -// FIXME handle date line wrap - -goog.provide('ol.control.Attribution'); +goog.provide('ol.structs.PriorityQueue'); goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.style'); -goog.require('ol'); -goog.require('ol.Attribution'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.events'); -goog.require('ol.events.EventType'); goog.require('ol.object'); -goog.require('ol.source.Tile'); /** - * @classdesc - * Control to show all the attributions associated with the layer sources - * in the map. This control is one of the default controls included in maps. - * By default it will show in the bottom right portion of the map, but this can - * be changed by using a css selector for `.ol-attribution`. + * Priority queue. + * + * The implementation is inspired from the Closure Library's Heap class and + * Python's heapq module. + * + * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html + * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.AttributionOptions=} opt_options Attribution options. - * @api stable + * @param {function(T): number} priorityFunction Priority function. + * @param {function(T): string} keyFunction Key function. + * @struct + * @template T */ -ol.control.Attribution = function(opt_options) { - - var options = opt_options ? opt_options : {}; +ol.structs.PriorityQueue = function(priorityFunction, keyFunction) { /** + * @type {function(T): number} * @private - * @type {Element} */ - this.ulElement_ = document.createElement('UL'); + this.priorityFunction_ = priorityFunction; /** + * @type {function(T): string} * @private - * @type {Element} */ - this.logoLi_ = document.createElement('LI'); - - this.ulElement_.appendChild(this.logoLi_); - goog.style.setElementShown(this.logoLi_, false); + this.keyFunction_ = keyFunction; /** + * @type {Array.} * @private - * @type {boolean} */ - this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + this.elements_ = []; /** + * @type {Array.} * @private - * @type {boolean} */ - this.collapsible_ = options.collapsible !== undefined ? - options.collapsible : true; - - if (!this.collapsible_) { - this.collapsed_ = false; - } - - var className = options.className !== undefined ? options.className : 'ol-attribution'; - - var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions'; - - var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB'; + this.priorities_ = []; /** + * @type {Object.} * @private - * @type {Node} */ - this.collapseLabel_ = typeof collapseLabel === 'string' ? - goog.dom.createDom('SPAN', {}, collapseLabel) : - collapseLabel; + this.queuedElements_ = {}; - var label = options.label !== undefined ? options.label : 'i'; +}; - /** - * @private - * @type {Node} - */ - this.label_ = typeof label === 'string' ? - goog.dom.createDom('SPAN', {}, label) : - label; - var activeLabel = (this.collapsible_ && !this.collapsed_) ? - this.collapseLabel_ : this.label_; - var button = goog.dom.createDom('BUTTON', { - 'type': 'button', - 'title': tipLabel - }, activeLabel); +/** + * @const + * @type {number} + */ +ol.structs.PriorityQueue.DROP = Infinity; - ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this); - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL + - (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + - (this.collapsible_ ? '' : ' ol-uncollapsible'); - var element = goog.dom.createDom('DIV', - cssClasses, this.ulElement_, button); +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.assertValid = function() { + var elements = this.elements_; + var priorities = this.priorities_; + var n = elements.length; + goog.asserts.assert(priorities.length == n); + var i, priority; + for (i = 0; i < (n >> 1) - 1; ++i) { + priority = priorities[i]; + goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)], + 'priority smaller than or equal to priority of left child (%s <= %s)', + priority, priorities[this.getLeftChildIndex_(i)]); + goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)], + 'priority smaller than or equal to priority of right child (%s <= %s)', + priority, priorities[this.getRightChildIndex_(i)]); + } +}; - var render = options.render ? options.render : ol.control.Attribution.render; - goog.base(this, { - element: element, - render: render, - target: options.target - }); +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.clear = function() { + this.elements_.length = 0; + this.priorities_.length = 0; + ol.object.clear(this.queuedElements_); +}; - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; - /** - * @private - * @type {Object.} - */ - this.attributionElements_ = {}; +/** + * Remove and return the highest-priority element. O(log N). + * @return {T} Element. + */ +ol.structs.PriorityQueue.prototype.dequeue = function() { + var elements = this.elements_; + goog.asserts.assert(elements.length > 0, + 'must have elements in order to be able to dequeue'); + var priorities = this.priorities_; + var element = elements[0]; + if (elements.length == 1) { + elements.length = 0; + priorities.length = 0; + } else { + elements[0] = elements.pop(); + priorities[0] = priorities.pop(); + this.siftUp_(0); + } + var elementKey = this.keyFunction_(element); + goog.asserts.assert(elementKey in this.queuedElements_, + 'key %s is not listed as queued', elementKey); + delete this.queuedElements_[elementKey]; + return element; +}; - /** - * @private - * @type {Object.} - */ - this.attributionElementRenderedVisible_ = {}; - /** - * @private - * @type {Object.} - */ - this.logoElements_ = {}; +/** + * Enqueue an element. O(log N). + * @param {T} element Element. + * @return {boolean} The element was added to the queue. + */ +ol.structs.PriorityQueue.prototype.enqueue = function(element) { + goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_), + 'key %s is already listed as queued', this.keyFunction_(element)); + var priority = this.priorityFunction_(element); + if (priority != ol.structs.PriorityQueue.DROP) { + this.elements_.push(element); + this.priorities_.push(priority); + this.queuedElements_[this.keyFunction_(element)] = true; + this.siftDown_(0, this.elements_.length - 1); + return true; + } + return false; +}; + +/** + * @return {number} Count. + */ +ol.structs.PriorityQueue.prototype.getCount = function() { + return this.elements_.length; }; -goog.inherits(ol.control.Attribution, ol.control.Control); /** - * @param {?olx.FrameState} frameState Frame state. - * @return {Array.>} Attributions. + * Gets the index of the left child of the node at the given index. + * @param {number} index The index of the node to get the left child for. + * @return {number} The index of the left child. + * @private */ -ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { - var i, ii, j, jj, tileRanges, source, sourceAttribution, - sourceAttributionKey, sourceAttributions, sourceKey; - var intersectsTileRange; - var layerStatesArray = frameState.layerStatesArray; - /** @type {Object.} */ - var attributions = ol.object.assign({}, frameState.attributions); - /** @type {Object.} */ - var hiddenAttributions = {}; - var projection = frameState.viewState.projection; - goog.asserts.assert(projection, 'projection of viewState required'); - for (i = 0, ii = layerStatesArray.length; i < ii; i++) { - source = layerStatesArray[i].layer.getSource(); - if (!source) { - continue; - } - sourceKey = goog.getUid(source).toString(); - sourceAttributions = source.getAttributions(); - if (!sourceAttributions) { - continue; - } - for (j = 0, jj = sourceAttributions.length; j < jj; j++) { - sourceAttribution = sourceAttributions[j]; - sourceAttributionKey = goog.getUid(sourceAttribution).toString(); - if (sourceAttributionKey in attributions) { - continue; - } - tileRanges = frameState.usedTiles[sourceKey]; - if (tileRanges) { - goog.asserts.assertInstanceof(source, ol.source.Tile, - 'source should be an ol.source.Tile'); - var tileGrid = source.getTileGridForProjection(projection); - goog.asserts.assert(tileGrid, 'tileGrid required for projection'); - intersectsTileRange = sourceAttribution.intersectsAnyTileRange( - tileRanges, tileGrid, projection); - } else { - intersectsTileRange = false; - } - if (intersectsTileRange) { - if (sourceAttributionKey in hiddenAttributions) { - delete hiddenAttributions[sourceAttributionKey]; - } - attributions[sourceAttributionKey] = sourceAttribution; - } else { - hiddenAttributions[sourceAttributionKey] = sourceAttribution; - } - } - } - return [attributions, hiddenAttributions]; +ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) { + return index * 2 + 1; }; /** - * Update the attribution element. - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.Attribution} - * @api + * Gets the index of the right child of the node at the given index. + * @param {number} index The index of the node to get the right child for. + * @return {number} The index of the right child. + * @private */ -ol.control.Attribution.render = function(mapEvent) { - this.updateElement_(mapEvent.frameState); +ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) { + return index * 2 + 2; }; /** + * Gets the index of the parent of the node at the given index. + * @param {number} index The index of the node to get the parent for. + * @return {number} The index of the parent. * @private - * @param {?olx.FrameState} frameState Frame state. */ -ol.control.Attribution.prototype.updateElement_ = function(frameState) { +ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) { + return (index - 1) >> 1; +}; - if (!frameState) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.element, false); - this.renderedVisible_ = false; - } - return; + +/** + * Make this a heap. O(N). + * @private + */ +ol.structs.PriorityQueue.prototype.heapify_ = function() { + var i; + for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { + this.siftUp_(i); } - - var attributions = this.getSourceAttributions(frameState); - /** @type {Object.} */ - var visibleAttributions = attributions[0]; - /** @type {Object.} */ - var hiddenAttributions = attributions[1]; - - var attributionElement, attributionKey; - for (attributionKey in this.attributionElements_) { - if (attributionKey in visibleAttributions) { - if (!this.attributionElementRenderedVisible_[attributionKey]) { - goog.style.setElementShown( - this.attributionElements_[attributionKey], true); - this.attributionElementRenderedVisible_[attributionKey] = true; - } - delete visibleAttributions[attributionKey]; - } else if (attributionKey in hiddenAttributions) { - if (this.attributionElementRenderedVisible_[attributionKey]) { - goog.style.setElementShown( - this.attributionElements_[attributionKey], false); - delete this.attributionElementRenderedVisible_[attributionKey]; - } - delete hiddenAttributions[attributionKey]; - } else { - goog.dom.removeNode(this.attributionElements_[attributionKey]); - delete this.attributionElements_[attributionKey]; - delete this.attributionElementRenderedVisible_[attributionKey]; - } - } - for (attributionKey in visibleAttributions) { - attributionElement = document.createElement('LI'); - attributionElement.innerHTML = - visibleAttributions[attributionKey].getHTML(); - this.ulElement_.appendChild(attributionElement); - this.attributionElements_[attributionKey] = attributionElement; - this.attributionElementRenderedVisible_[attributionKey] = true; - } - for (attributionKey in hiddenAttributions) { - attributionElement = document.createElement('LI'); - attributionElement.innerHTML = - hiddenAttributions[attributionKey].getHTML(); - goog.style.setElementShown(attributionElement, false); - this.ulElement_.appendChild(attributionElement); - this.attributionElements_[attributionKey] = attributionElement; - } - - var renderVisible = - !ol.object.isEmpty(this.attributionElementRenderedVisible_) || - !ol.object.isEmpty(frameState.logos); - if (this.renderedVisible_ != renderVisible) { - goog.style.setElementShown(this.element, renderVisible); - this.renderedVisible_ = renderVisible; - } - if (renderVisible && - ol.object.isEmpty(this.attributionElementRenderedVisible_)) { - this.element.classList.add('ol-logo-only'); - } else { - this.element.classList.remove('ol-logo-only'); - } - - this.insertLogos_(frameState); - -}; +}; /** - * @param {?olx.FrameState} frameState Frame state. - * @private + * @return {boolean} Is empty. */ -ol.control.Attribution.prototype.insertLogos_ = function(frameState) { - - var logo; - var logos = frameState.logos; - var logoElements = this.logoElements_; - - for (logo in logoElements) { - if (!(logo in logos)) { - goog.dom.removeNode(logoElements[logo]); - delete logoElements[logo]; - } - } - - var image, logoElement, logoKey; - for (logoKey in logos) { - var logoValue = logos[logoKey]; - if (logoValue instanceof HTMLElement) { - this.logoLi_.appendChild(logoValue); - logoElements[logoKey] = logoValue; - } - if (!(logoKey in logoElements)) { - image = new Image(); - image.src = logoKey; - if (logoValue === '') { - logoElement = image; - } else { - logoElement = goog.dom.createDom('A', { - 'href': logoValue - }); - logoElement.appendChild(image); - } - this.logoLi_.appendChild(logoElement); - logoElements[logoKey] = logoElement; - } - } - - goog.style.setElementShown(this.logoLi_, !ol.object.isEmpty(logos)); - +ol.structs.PriorityQueue.prototype.isEmpty = function() { + return this.elements_.length === 0; }; /** - * @param {Event} event The event to handle - * @private + * @param {string} key Key. + * @return {boolean} Is key queued. */ -ol.control.Attribution.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleToggle_(); +ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) { + return key in this.queuedElements_; }; /** - * @private + * @param {T} element Element. + * @return {boolean} Is queued. */ -ol.control.Attribution.prototype.handleToggle_ = function() { - this.element.classList.toggle('ol-collapsed'); - if (this.collapsed_) { - goog.dom.replaceNode(this.collapseLabel_, this.label_); - } else { - goog.dom.replaceNode(this.label_, this.collapseLabel_); - } - this.collapsed_ = !this.collapsed_; +ol.structs.PriorityQueue.prototype.isQueued = function(element) { + return this.isKeyQueued(this.keyFunction_(element)); }; /** - * Return `true` if the attribution is collapsible, `false` otherwise. - * @return {boolean} True if the widget is collapsible. - * @api stable + * @param {number} index The index of the node to move down. + * @private */ -ol.control.Attribution.prototype.getCollapsible = function() { - return this.collapsible_; -}; +ol.structs.PriorityQueue.prototype.siftUp_ = function(index) { + var elements = this.elements_; + var priorities = this.priorities_; + var count = elements.length; + var element = elements[index]; + var priority = priorities[index]; + var startIndex = index; + + while (index < (count >> 1)) { + var lIndex = this.getLeftChildIndex_(index); + var rIndex = this.getRightChildIndex_(index); + var smallerChildIndex = rIndex < count && + priorities[rIndex] < priorities[lIndex] ? + rIndex : lIndex; -/** - * Set whether the attribution should be collapsible. - * @param {boolean} collapsible True if the widget is collapsible. - * @api stable - */ -ol.control.Attribution.prototype.setCollapsible = function(collapsible) { - if (this.collapsible_ === collapsible) { - return; - } - this.collapsible_ = collapsible; - this.element.classList.toggle('ol-uncollapsible'); - if (!collapsible && this.collapsed_) { - this.handleToggle_(); + elements[index] = elements[smallerChildIndex]; + priorities[index] = priorities[smallerChildIndex]; + index = smallerChildIndex; } + + elements[index] = element; + priorities[index] = priority; + this.siftDown_(startIndex, index); }; /** - * Collapse or expand the attribution according to the passed parameter. Will - * not do anything if the attribution isn't collapsible or if the current - * collapsed state is already the one requested. - * @param {boolean} collapsed True if the widget is collapsed. - * @api stable + * @param {number} startIndex The index of the root. + * @param {number} index The index of the node to move up. + * @private */ -ol.control.Attribution.prototype.setCollapsed = function(collapsed) { - if (!this.collapsible_ || this.collapsed_ === collapsed) { - return; +ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) { + var elements = this.elements_; + var priorities = this.priorities_; + var element = elements[index]; + var priority = priorities[index]; + + while (index > startIndex) { + var parentIndex = this.getParentIndex_(index); + if (priorities[parentIndex] > priority) { + elements[index] = elements[parentIndex]; + priorities[index] = priorities[parentIndex]; + index = parentIndex; + } else { + break; + } } - this.handleToggle_(); + elements[index] = element; + priorities[index] = priority; }; /** - * Return `true` when the attribution is currently collapsed or `false` - * otherwise. - * @return {boolean} True if the widget is collapsed. - * @api stable + * FIXME empty description for jsdoc */ -ol.control.Attribution.prototype.getCollapsed = function() { - return this.collapsed_; +ol.structs.PriorityQueue.prototype.reprioritize = function() { + var priorityFunction = this.priorityFunction_; + var elements = this.elements_; + var priorities = this.priorities_; + var index = 0; + var n = elements.length; + var element, i, priority; + for (i = 0; i < n; ++i) { + element = elements[i]; + priority = priorityFunction(element); + if (priority == ol.structs.PriorityQueue.DROP) { + delete this.queuedElements_[this.keyFunction_(element)]; + } else { + priorities[index] = priority; + elements[index++] = element; + } + } + elements.length = index; + priorities.length = index; + this.heapify_(); }; -goog.provide('ol.control.Rotate'); +goog.provide('ol.TileQueue'); -goog.require('goog.dom'); +goog.require('goog.asserts'); goog.require('ol.events'); goog.require('ol.events.EventType'); -goog.require('ol'); -goog.require('ol.animation'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.easing'); +goog.require('ol.TileState'); +goog.require('ol.structs.PriorityQueue'); /** - * @classdesc - * A button control to reset rotation to 0. - * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css - * selector is added to the button when the rotation is 0. - * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.RotateOptions=} opt_options Rotate options. - * @api stable + * @extends {ol.structs.PriorityQueue.} + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + * @param {function(): ?} tileChangeCallback + * Function called on each tile change event. + * @struct */ -ol.control.Rotate = function(opt_options) { - - var options = opt_options ? opt_options : {}; - - var className = options.className !== undefined ? options.className : 'ol-rotate'; +ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) { - var label = options.label !== undefined ? options.label : '\u21E7'; + ol.structs.PriorityQueue.call( + this, + /** + * @param {Array} element Element. + * @return {number} Priority. + */ + function(element) { + return tilePriorityFunction.apply(null, element); + }, + /** + * @param {Array} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); /** - * @type {Element} * @private + * @type {function(): ?} */ - this.label_ = null; - - if (typeof label === 'string') { - this.label_ = goog.dom.createDom('SPAN', - 'ol-compass', label); - } else { - this.label_ = label; - this.label_.classList.add('ol-compass'); - } - - var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation'; - - var button = goog.dom.createDom('BUTTON', { - 'class': className + '-reset', - 'type' : 'button', - 'title': tipLabel - }, this.label_); - - ol.events.listen(button, ol.events.EventType.CLICK, - ol.control.Rotate.prototype.handleClick_, this); - - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL; - var element = goog.dom.createDom('DIV', cssClasses, button); - - var render = options.render ? options.render : ol.control.Rotate.render; - - this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined; - - goog.base(this, { - element: element, - render: render, - target: options.target - }); + this.tileChangeCallback_ = tileChangeCallback; /** - * @type {number} * @private + * @type {number} */ - this.duration_ = options.duration !== undefined ? options.duration : 250; + this.tilesLoading_ = 0; /** - * @type {boolean} * @private + * @type {!Object.} */ - this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true; + this.tilesLoadingKeys_ = {}; - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; +}; +ol.inherits(ol.TileQueue, ol.structs.PriorityQueue); - if (this.autoHide_) { - this.element.classList.add(ol.css.CLASS_HIDDEN); - } +/** + * @inheritDoc + */ +ol.TileQueue.prototype.enqueue = function(element) { + var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element); + if (added) { + var tile = element[0]; + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + } + return added; }; -goog.inherits(ol.control.Rotate, ol.control.Control); /** - * @param {Event} event The event to handle - * @private + * @return {number} Number of tiles loading. */ -ol.control.Rotate.prototype.handleClick_ = function(event) { - event.preventDefault(); - if (this.callResetNorth_ !== undefined) { - this.callResetNorth_(); - } else { - this.resetNorth_(); - } +ol.TileQueue.prototype.getTilesLoading = function() { + return this.tilesLoading_; }; /** - * @private + * @param {ol.events.Event} event Event. + * @protected */ -ol.control.Rotate.prototype.resetNorth_ = function() { - var map = this.getMap(); - var view = map.getView(); - if (!view) { - // the map does not have a view, so we can't act - // upon it - return; - } - var currentRotation = view.getRotation(); - if (currentRotation !== undefined) { - if (this.duration_ > 0) { - currentRotation = currentRotation % (2 * Math.PI); - if (currentRotation < -Math.PI) { - currentRotation += 2 * Math.PI; - } - if (currentRotation > Math.PI) { - currentRotation -= 2 * Math.PI; - } - map.beforeRender(ol.animation.rotate({ - rotation: currentRotation, - duration: this.duration_, - easing: ol.easing.easeOut - })); +ol.TileQueue.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + var state = tile.getState(); + if (state === ol.TileState.LOADED || state === ol.TileState.ERROR || + state === ol.TileState.EMPTY || state === ol.TileState.ABORT) { + ol.events.unlisten(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + var tileKey = tile.getKey(); + if (tileKey in this.tilesLoadingKeys_) { + delete this.tilesLoadingKeys_[tileKey]; + --this.tilesLoading_; } - view.setRotation(0); + this.tileChangeCallback_(); } + goog.asserts.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); }; /** - * Update the rotate control element. - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.Rotate} - * @api + * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. + * @param {number} maxNewLoads Maximum number of new tiles to load. */ -ol.control.Rotate.render = function(mapEvent) { - var frameState = mapEvent.frameState; - if (!frameState) { - return; - } - var rotation = frameState.viewState.rotation; - if (rotation != this.rotation_) { - var transform = 'rotate(' + rotation + 'rad)'; - if (this.autoHide_) { - var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN); - if (!contains && rotation === 0) { - this.element.classList.add(ol.css.CLASS_HIDDEN); - } else if (contains && rotation !== 0) { - this.element.classList.remove(ol.css.CLASS_HIDDEN); - } +ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { + var newLoads = 0; + var tile, tileKey; + while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads && + this.getCount() > 0) { + tile = /** @type {ol.Tile} */ (this.dequeue()[0]); + tileKey = tile.getKey(); + if (tile.getState() === ol.TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) { + this.tilesLoadingKeys_[tileKey] = true; + ++this.tilesLoading_; + ++newLoads; + tile.load(); } - this.label_.style.msTransform = transform; - this.label_.style.webkitTransform = transform; - this.label_.style.transform = transform; + goog.asserts.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); } - this.rotation_ = rotation; }; -goog.provide('ol.control.Zoom'); +goog.provide('ol.Kinetic'); -goog.require('goog.dom'); -goog.require('ol.events'); -goog.require('ol.events.EventType'); goog.require('ol.animation'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.easing'); /** * @classdesc - * A control with 2 buttons, one for zoom in and one for zoom out. - * This control is one of the default controls of a map. To style this control - * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. + * Implementation of inertial deceleration for map movement. * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.ZoomOptions=} opt_options Zoom options. - * @api stable + * @param {number} decay Rate of decay (must be negative). + * @param {number} minVelocity Minimum velocity (pixels/millisecond). + * @param {number} delay Delay to consider to calculate the kinetic + * initial values (milliseconds). + * @struct + * @api */ -ol.control.Zoom = function(opt_options) { - - var options = opt_options ? opt_options : {}; - - var className = options.className !== undefined ? options.className : 'ol-zoom'; - - var delta = options.delta !== undefined ? options.delta : 1; - - var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+'; - var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212'; - - var zoomInTipLabel = options.zoomInTipLabel !== undefined ? - options.zoomInTipLabel : 'Zoom in'; - var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ? - options.zoomOutTipLabel : 'Zoom out'; - - var inElement = goog.dom.createDom('BUTTON', { - 'class': className + '-in', - 'type' : 'button', - 'title': zoomInTipLabel - }, zoomInLabel); - - ol.events.listen(inElement, ol.events.EventType.CLICK, - ol.control.Zoom.prototype.handleClick_.bind(this, delta)); +ol.Kinetic = function(decay, minVelocity, delay) { - var outElement = goog.dom.createDom('BUTTON', { - 'class': className + '-out', - 'type' : 'button', - 'title': zoomOutTipLabel - }, zoomOutLabel); + /** + * @private + * @type {number} + */ + this.decay_ = decay; - ol.events.listen(outElement, ol.events.EventType.CLICK, - ol.control.Zoom.prototype.handleClick_.bind(this, -delta)); + /** + * @private + * @type {number} + */ + this.minVelocity_ = minVelocity; - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL; - var element = goog.dom.createDom('DIV', cssClasses, inElement, outElement); + /** + * @private + * @type {number} + */ + this.delay_ = delay; - goog.base(this, { - element: element, - target: options.target - }); + /** + * @private + * @type {Array.} + */ + this.points_ = []; /** - * @type {number} * @private + * @type {number} */ - this.duration_ = options.duration !== undefined ? options.duration : 250; + this.angle_ = 0; + /** + * @private + * @type {number} + */ + this.initialVelocity_ = 0; }; -goog.inherits(ol.control.Zoom, ol.control.Control); /** - * @param {number} delta Zoom delta. - * @param {Event} event The event to handle - * @private + * FIXME empty description for jsdoc */ -ol.control.Zoom.prototype.handleClick_ = function(delta, event) { - event.preventDefault(); - this.zoomByDelta_(delta); +ol.Kinetic.prototype.begin = function() { + this.points_.length = 0; + this.angle_ = 0; + this.initialVelocity_ = 0; }; /** - * @param {number} delta Zoom delta. - * @private + * @param {number} x X. + * @param {number} y Y. */ -ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { - var map = this.getMap(); - var view = map.getView(); - if (!view) { - // the map does not have a view, so we can't act - // upon it - return; - } - var currentResolution = view.getResolution(); - if (currentResolution) { - if (this.duration_ > 0) { - map.beforeRender(ol.animation.zoom({ - resolution: currentResolution, - duration: this.duration_, - easing: ol.easing.easeOut - })); - } - var newResolution = view.constrainResolution(currentResolution, delta); - view.setResolution(newResolution); - } +ol.Kinetic.prototype.update = function(x, y) { + this.points_.push(x, y, Date.now()); }; -goog.provide('ol.control'); - -goog.require('ol'); -goog.require('ol.Collection'); -goog.require('ol.control.Attribution'); -goog.require('ol.control.Rotate'); -goog.require('ol.control.Zoom'); - /** - * Set of controls included in maps by default. Unless configured otherwise, - * this returns a collection containing an instance of each of the following - * controls: - * * {@link ol.control.Zoom} - * * {@link ol.control.Rotate} - * * {@link ol.control.Attribution} - * - * @param {olx.control.DefaultsOptions=} opt_options Defaults options. - * @return {ol.Collection.} Controls. - * @api stable + * @return {boolean} Whether we should do kinetic animation. */ -ol.control.defaults = function(opt_options) { - - var options = opt_options ? opt_options : {}; - - var controls = new ol.Collection(); - - var zoomControl = options.zoom !== undefined ? options.zoom : true; - if (zoomControl) { - controls.push(new ol.control.Zoom(options.zoomOptions)); +ol.Kinetic.prototype.end = function() { + if (this.points_.length < 6) { + // at least 2 points are required (i.e. there must be at least 6 elements + // in the array) + return false; } - - var rotateControl = options.rotate !== undefined ? options.rotate : true; - if (rotateControl) { - controls.push(new ol.control.Rotate(options.rotateOptions)); + var delay = Date.now() - this.delay_; + var lastIndex = this.points_.length - 3; + if (this.points_[lastIndex + 2] < delay) { + // the last tracked point is too old, which means that the user stopped + // panning before releasing the map + return false; } - var attributionControl = options.attribution !== undefined ? - options.attribution : true; - if (attributionControl) { - controls.push(new ol.control.Attribution(options.attributionOptions)); + // get the first point which still falls into the delay time + var firstIndex = lastIndex - 3; + while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) { + firstIndex -= 3; } - - return controls; - -}; - -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Functions for managing full screen status of the DOM. - * - */ - -goog.provide('goog.dom.fullscreen'); -goog.provide('goog.dom.fullscreen.EventType'); - -goog.require('goog.dom'); -goog.require('goog.userAgent'); - - -/** - * Event types for full screen. - * @enum {string} - */ -goog.dom.fullscreen.EventType = { - /** Dispatched by the Document when the fullscreen status changes. */ - CHANGE: (function() { - if (goog.userAgent.WEBKIT) { - return 'webkitfullscreenchange'; - } - if (goog.userAgent.GECKO) { - return 'mozfullscreenchange'; - } - if (goog.userAgent.IE) { - return 'MSFullscreenChange'; - } - // Opera 12-14, and W3C standard (Draft): - // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html - return 'fullscreenchange'; - })() + var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2]; + var dx = this.points_[lastIndex] - this.points_[firstIndex]; + var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1]; + this.angle_ = Math.atan2(dy, dx); + this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration; + return this.initialVelocity_ > this.minVelocity_; }; /** - * Determines if full screen is supported. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {boolean} True iff full screen is supported. + * @param {ol.Coordinate} source Source coordinate for the animation. + * @return {ol.PreRenderFunction} Pre-render function for kinetic animation. */ -goog.dom.fullscreen.isSupported = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - var body = doc.body; - return !!( - body.webkitRequestFullscreen || - (body.mozRequestFullScreen && doc.mozFullScreenEnabled) || - (body.msRequestFullscreen && doc.msFullscreenEnabled) || - (body.requestFullscreen && doc.fullscreenEnabled)); +ol.Kinetic.prototype.pan = function(source) { + var decay = this.decay_; + var initialVelocity = this.initialVelocity_; + var velocity = this.minVelocity_ - initialVelocity; + var duration = this.getDuration_(); + var easingFunction = ( + /** + * @param {number} t T. + * @return {number} Easing. + */ + function(t) { + return initialVelocity * (Math.exp((decay * t) * duration) - 1) / + velocity; + }); + return ol.animation.pan({ + source: source, + duration: duration, + easing: easingFunction + }); }; /** - * Requests putting the element in full screen. - * @param {!Element} element The element to put full screen. + * @private + * @return {number} Duration of animation (milliseconds). */ -goog.dom.fullscreen.requestFullScreen = function(element) { - if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - } else if (element.mozRequestFullScreen) { - element.mozRequestFullScreen(); - } else if (element.msRequestFullscreen) { - element.msRequestFullscreen(); - } else if (element.requestFullscreen) { - element.requestFullscreen(); - } +ol.Kinetic.prototype.getDuration_ = function() { + return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_; }; /** - * Requests putting the element in full screen with full keyboard access. - * @param {!Element} element The element to put full screen. + * @return {number} Total distance travelled (pixels). */ -goog.dom.fullscreen.requestFullScreenWithKeys = function(element) { - if (element.mozRequestFullScreenWithKeys) { - element.mozRequestFullScreenWithKeys(); - } else if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - } else { - goog.dom.fullscreen.requestFullScreen(element); - } +ol.Kinetic.prototype.getDistance = function() { + return (this.minVelocity_ - this.initialVelocity_) / this.decay_; }; /** - * Exits full screen. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. + * @return {number} Angle of the kinetic panning animation (radians). */ -goog.dom.fullscreen.exitFullScreen = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - if (doc.webkitCancelFullScreen) { - doc.webkitCancelFullScreen(); - } else if (doc.mozCancelFullScreen) { - doc.mozCancelFullScreen(); - } else if (doc.msExitFullscreen) { - doc.msExitFullscreen(); - } else if (doc.exitFullscreen) { - doc.exitFullscreen(); - } +ol.Kinetic.prototype.getAngle = function() { + return this.angle_; }; +// FIXME factor out key precondition (shift et. al) -/** - * Determines if the document is full screen. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {boolean} Whether the document is full screen. - */ -goog.dom.fullscreen.isFullScreen = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - // IE 11 doesn't have similar boolean property, so check whether - // document.msFullscreenElement is null instead. - return !!( - doc.webkitIsFullScreen || doc.mozFullScreen || doc.msFullscreenElement || - doc.fullscreenElement); -}; - +goog.provide('ol.interaction.Interaction'); +goog.provide('ol.interaction.InteractionProperty'); -/** - * Get the root element in full screen mode. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {?Element} The root element in full screen mode. - */ -goog.dom.fullscreen.getFullScreenElement = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - var element_list = [ - doc.webkitFullscreenElement, doc.mozFullScreenElement, - doc.msFullscreenElement, doc.fullscreenElement - ]; - for (var i = 0; i < element_list.length; i++) { - if (element_list[i] != null) { - return element_list[i]; - } - } - return null; -}; +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.Object'); +goog.require('ol.animation'); +goog.require('ol.easing'); /** - * Gets the document object of the dom. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {!Document} The dom document. - * @private + * @enum {string} */ -goog.dom.fullscreen.getDocument_ = function(opt_domHelper) { - return opt_domHelper ? opt_domHelper.getDocument() : - goog.dom.getDomHelper().getDocument(); +ol.interaction.InteractionProperty = { + ACTIVE: 'active' }; -goog.provide('ol.control.FullScreen'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.fullscreen'); -goog.require('goog.dom.fullscreen.EventType'); -goog.require('ol.events'); -goog.require('ol.events.EventType'); -goog.require('ol'); -goog.require('ol.control.Control'); -goog.require('ol.css'); - /** * @classdesc - * Provides a button that when clicked fills up the full screen with the map. - * The full screen source element is by default the element containing the map viewport unless - * overriden by providing the `source` option. In which case, the dom - * element introduced using this parameter will be displayed in full screen. - * - * When in full screen mode, a close button is shown to exit full screen mode. - * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to - * toggle the map in full screen mode. - * + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * User actions that change the state of the map. Some are similar to controls, + * but are not associated with a DOM element. + * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as + * {@link ol.control.Zoom}, but triggered by a keyboard event not a button + * element event. + * Although interactions do not have a DOM element, some of them do render + * vectors and so are visible on the screen. * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.FullScreenOptions=} opt_options Options. - * @api stable + * @param {olx.interaction.InteractionOptions} options Options. + * @extends {ol.Object} + * @api */ -ol.control.FullScreen = function(opt_options) { - - var options = opt_options ? opt_options : {}; - - /** - * @private - * @type {string} - */ - this.cssClassName_ = options.className !== undefined ? options.className : - 'ol-full-screen'; +ol.interaction.Interaction = function(options) { - var label = options.label !== undefined ? options.label : '\u2922'; + ol.Object.call(this); /** * @private - * @type {Node} + * @type {ol.Map} */ - this.labelNode_ = typeof label === 'string' ? - document.createTextNode(label) : label; + this.map_ = null; - var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7'; + this.setActive(true); /** - * @private - * @type {Node} + * @type {function(ol.MapBrowserEvent):boolean} */ - this.labelActiveNode_ = typeof labelActive === 'string' ? - document.createTextNode(labelActive) : labelActive; + this.handleEvent = options.handleEvent; - var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen'; - var button = goog.dom.createDom('BUTTON', { - 'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(), - 'type': 'button', - 'title': tipLabel - }, this.labelNode_); +}; +ol.inherits(ol.interaction.Interaction, ol.Object); - ol.events.listen(button, ol.events.EventType.CLICK, - this.handleClick_, this); - var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + - ' ' + ol.css.CLASS_CONTROL + ' ' + - (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : ''); - var element = goog.dom.createDom('DIV', cssClasses, button); +/** + * Return whether the interaction is currently active. + * @return {boolean} `true` if the interaction is active, `false` otherwise. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.getActive = function() { + return /** @type {boolean} */ ( + this.get(ol.interaction.InteractionProperty.ACTIVE)); +}; - goog.base(this, { - element: element, - target: options.target - }); - /** - * @private - * @type {boolean} - */ - this.keys_ = options.keys !== undefined ? options.keys : false; +/** + * Get the map associated with this interaction. + * @return {ol.Map} Map. + * @api + */ +ol.interaction.Interaction.prototype.getMap = function() { + return this.map_; +}; - /** - * @private - * @type {Element|string|undefined} - */ - this.source_ = options.source; +/** + * Activate or deactivate the interaction. + * @param {boolean} active Active. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.setActive = function(active) { + this.set(ol.interaction.InteractionProperty.ACTIVE, active); }; -goog.inherits(ol.control.FullScreen, ol.control.Control); /** - * @param {Event} event The event to handle - * @private + * Remove the interaction from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. */ -ol.control.FullScreen.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleFullScreen_(); +ol.interaction.Interaction.prototype.setMap = function(map) { + this.map_ = map; }; /** - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {ol.Coordinate} delta Delta. + * @param {number=} opt_duration Duration. */ -ol.control.FullScreen.prototype.handleFullScreen_ = function() { - if (!goog.dom.fullscreen.isSupported()) { - return; - } - var map = this.getMap(); - if (!map) { - return; - } - if (goog.dom.fullscreen.isFullScreen()) { - goog.dom.fullscreen.exitFullScreen(); - } else { - var element = this.source_ ? - goog.dom.getElement(this.source_) : map.getTargetElement(); - goog.asserts.assert(element, 'element should be defined'); - if (this.keys_) { - goog.dom.fullscreen.requestFullScreenWithKeys(element); - } else { - goog.dom.fullscreen.requestFullScreen(element); +ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) { + var currentCenter = view.getCenter(); + if (currentCenter) { + if (opt_duration && opt_duration > 0) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.linear + })); } + var center = view.constrainCenter( + [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]); + view.setCenter(center); } }; /** - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { - var button = this.element.firstElementChild; - var map = this.getMap(); - if (goog.dom.fullscreen.isFullScreen()) { - button.className = this.cssClassName_ + '-true'; - goog.dom.replaceNode(this.labelActiveNode_, this.labelNode_); - } else { - button.className = this.cssClassName_ + '-false'; - goog.dom.replaceNode(this.labelNode_, this.labelActiveNode_); - } - if (map) { - map.updateSize(); - } +ol.interaction.Interaction.rotate = function(map, view, rotation, opt_anchor, opt_duration) { + rotation = view.constrainRotation(rotation, 0); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation, opt_anchor, opt_duration); }; /** - * @inheritDoc - * @api stable + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.control.FullScreen.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - if (map) { - this.listenerKeys.push( - ol.events.listen(ol.global.document, goog.dom.fullscreen.EventType.CHANGE, - this.handleFullScreenChange_, this) - ); +ol.interaction.Interaction.rotateWithoutConstraints = function(map, view, rotation, opt_anchor, opt_duration) { + if (rotation !== undefined) { + var currentRotation = view.getRotation(); + var currentCenter = view.getCenter(); + if (currentRotation !== undefined && currentCenter && + opt_duration && opt_duration > 0) { + map.beforeRender(ol.animation.rotate({ + rotation: currentRotation, + duration: opt_duration, + easing: ol.easing.easeOut + })); + if (opt_anchor) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.easeOut + })); + } + } + view.rotate(rotation, opt_anchor); } }; -// FIXME should listen on appropriate pane, once it is defined -goog.provide('ol.control.MousePosition'); +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + * @param {number=} opt_direction Zooming direction; > 0 indicates + * zooming out, in which case the constraints system will select + * the largest nearest resolution; < 0 indicates zooming in, in + * which case the constraints system will select the smallest + * nearest resolution; == 0 indicates that the zooming direction + * is unknown/not relevant, in which case the constraints system + * will select the nearest resolution. If not defined 0 is + * assumed. + */ +ol.interaction.Interaction.zoom = function(map, view, resolution, opt_anchor, opt_duration, opt_direction) { + resolution = view.constrainResolution(resolution, 0, opt_direction); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; -goog.require('ol.events'); -goog.require('ol.events.EventType'); -goog.require('ol.Object'); -goog.require('ol.control.Control'); -goog.require('ol.proj'); -goog.require('ol.proj.Projection'); + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number} delta Delta from previous zoom level. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.zoomByDelta = function(map, view, delta, opt_anchor, opt_duration) { + var currentResolution = view.getResolution(); + var resolution = view.constrainResolution(currentResolution, delta, 0); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; /** - * @enum {string} + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.control.MousePositionProperty = { - PROJECTION: 'projection', - COORDINATE_FORMAT: 'coordinateFormat' +ol.interaction.Interaction.zoomWithoutConstraints = function(map, view, resolution, opt_anchor, opt_duration) { + if (resolution) { + var currentResolution = view.getResolution(); + var currentCenter = view.getCenter(); + if (currentResolution !== undefined && currentCenter && + resolution !== currentResolution && + opt_duration && opt_duration > 0) { + map.beforeRender(ol.animation.zoom({ + resolution: currentResolution, + duration: opt_duration, + easing: ol.easing.easeOut + })); + if (opt_anchor) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.easeOut + })); + } + } + if (opt_anchor) { + var center = view.calculateCenterZoom(resolution, opt_anchor); + view.setCenter(center); + } + view.setResolution(resolution); + } }; +goog.provide('ol.interaction.DoubleClickZoom'); + +goog.require('goog.asserts'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.interaction.Interaction'); + /** * @classdesc - * A control to show the 2D coordinates of the mouse cursor. By default, these - * are in the view projection, but can be in any supported projection. - * By default the control is shown in the top right corner of the map, but this - * can be changed by using the css selector `.ol-mouse-position`. + * Allows the user to zoom by double-clicking on the map. * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.MousePositionOptions=} opt_options Mouse position - * options. + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options. * @api stable */ -ol.control.MousePosition = function(opt_options) { +ol.interaction.DoubleClickZoom = function(opt_options) { var options = opt_options ? opt_options : {}; - var element = document.createElement('DIV'); - element.className = options.className !== undefined ? options.className : 'ol-mouse-position' - - var render = options.render ? - options.render : ol.control.MousePosition.render; - - goog.base(this, { - element: element, - render: render, - target: options.target - }); - - ol.events.listen(this, - ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION), - this.handleProjectionChanged_, this); - - if (options.coordinateFormat) { - this.setCoordinateFormat(options.coordinateFormat); - } - if (options.projection) { - this.setProjection(ol.proj.get(options.projection)); - } - - /** - * @private - * @type {string} - */ - this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : ''; - - /** - * @private - * @type {string} - */ - this.renderedHTML_ = element.innerHTML; - /** * @private - * @type {ol.proj.Projection} + * @type {number} */ - this.mapProjection_ = null; + this.delta_ = options.delta ? options.delta : 1; - /** - * @private - * @type {?ol.TransformFunction} - */ - this.transform_ = null; + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.DoubleClickZoom.handleEvent + }); /** * @private - * @type {ol.Pixel} + * @type {number} */ - this.lastMouseMovePixel_ = null; + this.duration_ = options.duration !== undefined ? options.duration : 250; }; -goog.inherits(ol.control.MousePosition, ol.control.Control); +ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction); /** - * Update the mouseposition element. - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.MousePosition} + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * doubleclick) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DoubleClickZoom} * @api */ -ol.control.MousePosition.render = function(mapEvent) { - var frameState = mapEvent.frameState; - if (!frameState) { - this.mapProjection_ = null; - } else { - if (this.mapProjection_ != frameState.viewState.projection) { - this.mapProjection_ = frameState.viewState.projection; - this.transform_ = null; - } +ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + var browserEvent = mapBrowserEvent.originalEvent; + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) { + var map = mapBrowserEvent.map; + var anchor = mapBrowserEvent.coordinate; + var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; + var view = map.getView(); + goog.asserts.assert(view, 'map must have a view'); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, anchor, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; } - this.updateHTML_(this.lastMouseMovePixel_); + return !stopEvent; }; +goog.provide('ol.events.condition'); + +goog.require('goog.asserts'); +goog.require('ol.functions'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.MapBrowserPointerEvent'); + /** - * @private + * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when + * additionally the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt key is pressed. + * @api stable */ -ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { - this.transform_ = null; +ol.events.condition.altKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); }; /** - * Return the coordinate format type used to render the current position or - * undefined. - * @return {ol.CoordinateFormatType|undefined} The format to render the current - * position in. - * @observable + * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise + * (e.g. when additionally the platform-modifier-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt and shift keys are pressed. * @api stable */ -ol.control.MousePosition.prototype.getCoordinateFormat = function() { - return /** @type {ol.CoordinateFormatType|undefined} */ ( - this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT)); +ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); }; /** - * Return the projection that is used to report the mouse position. - * @return {ol.proj.Projection|undefined} The projection to report mouse - * position in. - * @observable + * Return always true. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True. + * @function * @api stable */ -ol.control.MousePosition.prototype.getProjection = function() { - return /** @type {ol.proj.Projection|undefined} */ ( - this.get(ol.control.MousePositionProperty.PROJECTION)); -}; +ol.events.condition.always = ol.functions.TRUE; /** - * @param {Event} event Browser event. - * @protected + * Return `true` if the event is a `click` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `click` event. + * @api stable */ -ol.control.MousePosition.prototype.handleMouseMove = function(event) { - var map = this.getMap(); - this.lastMouseMovePixel_ = map.getEventPixel(event); - this.updateHTML_(this.lastMouseMovePixel_); +ol.events.condition.click = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK; }; /** - * @param {Event} event Browser event. - * @protected + * Return `true` if the event has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} The result. */ -ol.control.MousePosition.prototype.handleMouseOut = function(event) { - this.updateHTML_(null); - this.lastMouseMovePixel_ = null; +ol.events.condition.mouseActionButton = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return originalEvent.button == 0 && + !(goog.userAgent.WEBKIT && ol.has.MAC && originalEvent.ctrlKey); }; /** - * @inheritDoc + * Return always false. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} False. + * @function * @api stable */ -ol.control.MousePosition.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - if (map) { - var viewport = map.getViewport(); - this.listenerKeys.push( - ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE, - this.handleMouseMove, this), - ol.events.listen(viewport, ol.events.EventType.MOUSEOUT, - this.handleMouseOut, this) - ); - } -}; +ol.events.condition.never = ol.functions.FALSE; /** - * Set the coordinate format type used to render the current position. - * @param {ol.CoordinateFormatType} format The format to render the current - * position in. - * @observable - * @api stable + * Return `true` if the browser event is a `pointermove` event, `false` + * otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the browser event is a `pointermove` event. + * @api */ -ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { - this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format); +ol.events.condition.pointerMove = function(mapBrowserEvent) { + return mapBrowserEvent.type == 'pointermove'; }; /** - * Set the projection that is used to report the mouse position. - * @param {ol.proj.Projection} projection The projection to report mouse - * position in. - * @observable + * Return `true` if the event is a map `singleclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `singleclick` event. * @api stable */ -ol.control.MousePosition.prototype.setProjection = function(projection) { - this.set(ol.control.MousePositionProperty.PROJECTION, projection); +ol.events.condition.singleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; }; /** - * @param {?ol.Pixel} pixel Pixel. - * @private + * Return `true` if the event is a map `dblclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `dblclick` event. + * @api stable */ -ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { - var html = this.undefinedHTML_; - if (pixel && this.mapProjection_) { - if (!this.transform_) { - var projection = this.getProjection(); - if (projection) { - this.transform_ = ol.proj.getTransformFromProjections( - this.mapProjection_, projection); - } else { - this.transform_ = ol.proj.identityTransform; - } - } - var map = this.getMap(); - var coordinate = map.getCoordinateFromPixel(pixel); - if (coordinate) { - this.transform_(coordinate, coordinate); - var coordinateFormat = this.getCoordinateFormat(); - if (coordinateFormat) { - html = coordinateFormat(coordinate); - } else { - html = coordinate.toString(); - } - } - } - if (!this.renderedHTML_ || html != this.renderedHTML_) { - this.element.innerHTML = html; - this.renderedHTML_ = html; - } +ol.events.condition.doubleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK; }; -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A global registry for entry points into a program, - * so that they can be instrumented. Each module should register their - * entry points with this registry. Designed to be compiled out - * if no instrumentation is requested. - * - * Entry points may be registered before or after a call to - * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered - * later, the existing monitor will instrument the new entry point. + * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is + * pressed. * - * @author nicksantos@google.com (Nick Santos) + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if there no modifier keys are pressed. + * @api stable */ - -goog.provide('goog.debug.EntryPointMonitor'); -goog.provide('goog.debug.entryPointRegistry'); - -goog.require('goog.asserts'); - +ol.events.condition.noModifierKeys = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; /** - * @interface + * Return `true` if only the platform-modifier-key (the meta-key on Mac, + * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally + * the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the platform modifier key is pressed. + * @api stable */ -goog.debug.EntryPointMonitor = function() {}; +ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; /** - * Instruments a function. + * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when + * additionally the alt-key is pressed). * - * @param {!Function} fn A function to instrument. - * @return {!Function} The instrumented function. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the shift key is pressed. + * @api stable */ -goog.debug.EntryPointMonitor.prototype.wrap; +ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); +}; /** - * Try to remove an instrumentation wrapper created by this monitor. - * If the function passed to unwrap is not a wrapper created by this - * monitor, then we will do nothing. - * - * Notice that some wrappers may not be unwrappable. For example, if other - * monitors have applied their own wrappers, then it will be impossible to - * unwrap them because their wrappers will have captured our wrapper. - * - * So it is important that entry points are unwrapped in the reverse - * order that they were wrapped. + * Return `true` if the target element is not editable, i.e. not a ``-, + * `