From ecbc44b1cbbe250d3c73a336d6670f694e3a62c3 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Mon, 20 Aug 2018 19:40:49 -0400 Subject: [PATCH 01/26] Add support for linear filtering of floating point textures. --- Source/Renderer/Context.js | 45 ++++++++- Source/Renderer/CubeMap.js | 15 +-- Source/Renderer/Texture.js | 9 +- Specs/Renderer/CubeMapSpec.js | 173 ++++++++++++++++++++++++++++++++++ Specs/Renderer/TextureSpec.js | 163 ++++++++++++++++++++++++++------ 5 files changed, 362 insertions(+), 43 deletions(-) diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 575098f04cd8..30968b0fc632 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -276,11 +276,15 @@ define([ this._blendMinmax = !!getExtension(gl, ['EXT_blend_minmax']); this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']); this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']); - this._textureFloat = !!getExtension(gl, ['OES_texture_float']); - this._textureHalfFloat = !!getExtension(gl, ['OES_texture_half_float']); this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']); this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']); + this._textureFloat = !!getExtension(gl, ['OES_texture_float']); + this._textureHalfFloat = !!getExtension(gl, ['OES_texture_half_float']); + + this._textureFloatLinear = !!getExtension(gl, ['OES_texture_float_linear']); + this._textureHalfFloatLinear = !!getExtension(gl, ['OES_texture_half_float_linear']); + this._colorBufferFloat = !!getExtension(gl, ['EXT_color_buffer_float', 'WEBGL_color_buffer_float']); this._colorBufferHalfFloat = !!getExtension(gl, ['EXT_color_buffer_half_float']); @@ -566,7 +570,7 @@ define([ }, /** - * true if OES_texture_float is supported. This extension provides + * true if OES_texture_float is supported. This extension provides * access to floating point textures that, for example, can be attached to framebuffers for high dynamic range. * @memberof Context.prototype * @type {Boolean} @@ -579,7 +583,7 @@ define([ }, /** - * true if OES_texture_half_float is supported. This extension provides + * true if OES_texture_half_float is supported. This extension provides * access to floating point textures that, for example, can be attached to framebuffers for high dynamic range. * @memberof Context.prototype * @type {Boolean} @@ -591,6 +595,39 @@ define([ } }, + /** + * true if OES_texture_float_linear is supported. This extension provides + * access to linear sampling methods for minification and magnification filters of floating-point textures. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/} + */ + textureFloatLinear : { + get : function() { + return this._textureFloatLinear; + } + }, + + /** + * true if OES_texture_half_float_linear is supported. This extension provides + * access to linear sampling methods for minification and magnification filters of half floating-point textures. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/OES_texture_half_float_linear/} + */ + textureHalfFloatLinear : { + get : function() { + return this._textureHalfFloatLinear; + } + }, + + /** + * true if EXT_texture_filter_anisotropic is supported. This extension provides + * access to anisotropic filtering for textured surfaces at an oblique angle from the viewer. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/} + */ textureFilterAnisotropic : { get : function() { return !!this._textureFilterAnisotropic; diff --git a/Source/Renderer/CubeMap.js b/Source/Renderer/CubeMap.js index 74f96802ceac..cb81ea833522 100644 --- a/Source/Renderer/CubeMap.js +++ b/Source/Renderer/CubeMap.js @@ -162,7 +162,7 @@ define([ } gl.bindTexture(textureTarget, null); - this._gl = gl; + this._context = context; this._textureFilterAnisotropic = context._textureFilterAnisotropic; this._textureTarget = textureTarget; this._texture = texture; @@ -231,13 +231,16 @@ define([ (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); - // float textures only support nearest filtering, so override the sampler's settings - if (this._pixelDatatype === PixelDatatype.FLOAT || this._pixelDatatype === PixelDatatype.HALF_FLOAT) { + var context = this._context; + var pixelDatatype = this._pixelDatatype; + + // float textures only support nearest filtering unless the linear extensions are supported, so override the sampler's settings + if ((pixelDatatype === PixelDatatype.FLOAT && !context.textureFloatLinear) || (pixelDatatype === PixelDatatype.HALF_FLOAT && !context.textureHalfFloatLinear)) { minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST; magnificationFilter = TextureMagnificationFilter.NEAREST; } - var gl = this._gl; + var gl = context._gl; var target = this._textureTarget; gl.activeTexture(gl.TEXTURE0); @@ -332,7 +335,7 @@ define([ this._hasMipmap = true; - var gl = this._gl; + var gl = this._context._gl; var target = this._textureTarget; gl.hint(gl.GENERATE_MIPMAP_HINT, hint); gl.activeTexture(gl.TEXTURE0); @@ -346,7 +349,7 @@ define([ }; CubeMap.prototype.destroy = function() { - this._gl.deleteTexture(this._texture); + this._context._gl.deleteTexture(this._texture); this._positiveX = destroyObject(this._positiveX); this._negativeX = destroyObject(this._negativeX); this._positiveY = destroyObject(this._positiveY); diff --git a/Source/Renderer/Texture.js b/Source/Renderer/Texture.js index b89f8db0df0c..609798d7a9fe 100644 --- a/Source/Renderer/Texture.js +++ b/Source/Renderer/Texture.js @@ -385,13 +385,16 @@ define([ (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); - // float textures only support nearest filtering, so override the sampler's settings - if (this._pixelDatatype === PixelDatatype.FLOAT || this._pixelDatatype === PixelDatatype.HALF_FLOAT) { + var context = this._context; + var pixelDatatype = this._pixelDatatype; + + // float textures only support nearest filtering unless the linear extensions are supported, so override the sampler's settings + if ((pixelDatatype === PixelDatatype.FLOAT && !context.textureFloatLinear) || (pixelDatatype === PixelDatatype.HALF_FLOAT && !context.textureHalfFloatLinear)) { minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST; magnificationFilter = TextureMagnificationFilter.NEAREST; } - var gl = this._context._gl; + var gl = context._gl; var target = this._textureTarget; gl.activeTexture(gl.TEXTURE0); diff --git a/Specs/Renderer/CubeMapSpec.js b/Specs/Renderer/CubeMapSpec.js index f6e45cd62754..a917af2eb752 100644 --- a/Specs/Renderer/CubeMapSpec.js +++ b/Specs/Renderer/CubeMapSpec.js @@ -384,6 +384,91 @@ defineSuite([ }); }); + it('creates a cube map with floating-point textures and linear filtering', function() { + if (!context.floatingPointTexture) { + return; + } + + var positiveXColor = new Color(0.0, 1.0, 1.0, 1.0); + var negativeXColor = new Color(0.0, 0.0, 1.0, 1.0); + var positiveYColor = new Color(0.0, 1.0, 0.0, 1.0); + var negativeYColor = new Color(1.0, 0.0, 0.0, 1.0); + var positiveZColor = new Color(1.0, 0.0, 1.0, 1.0); + var negativeZColor = new Color(1.0, 1.0, 0.0, 1.0); + + cubeMap = new CubeMap({ + context : context, + source : { + positiveX : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([positiveXColor.red, positiveXColor.green, positiveXColor.blue, positiveXColor.alpha]) + }, + negativeX : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([negativeXColor.red, negativeXColor.green, negativeXColor.blue, negativeXColor.alpha]) + }, + positiveY : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([positiveYColor.red, positiveYColor.green, positiveYColor.blue, positiveYColor.alpha]) + }, + negativeY : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([negativeYColor.red, negativeYColor.green, negativeYColor.blue, negativeYColor.alpha]) + }, + positiveZ : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([positiveZColor.red, positiveZColor.green, positiveZColor.blue, positiveZColor.alpha]) + }, + negativeZ : { + width : 1, + height : 1, + arrayBufferView : new Float32Array([negativeZColor.red, negativeZColor.green, negativeZColor.blue, negativeZColor.alpha]) + } + }, + pixelDatatype : PixelDatatype.FLOAT, + sampler : new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.LINEAR, + magnificationFilter : TextureMagnificationFilter.LINEAR + }) + }); + + var fs = + 'uniform samplerCube u_texture;' + + 'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }'; + + var uniformMap = { + u_texture : function() { + return cubeMap; + } + }; + + if (!context.textureFloatLinear) { + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(positiveYColor.toBytes()); + } else { + Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor); + Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor); + var color = Color.add(positiveXColor, positiveYColor, positiveXColor); + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(color.toBytes()); + } + }); + it('creates a cube map with half floating-point textures', function() { if (!context.halfFloatingPointTexture) { return; @@ -396,6 +481,94 @@ defineSuite([ var positiveZFloats = [13926, 14541, 12902, 15360]; var negativeZFloats = [14541, 12902, 13926, 15360]; + var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0); + var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0); + + cubeMap = new CubeMap({ + context : context, + source : { + positiveX : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(positiveXFloats) + }, + negativeX : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(negativeXFloats) + }, + positiveY : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(positiveYFloats) + }, + negativeY : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(negativeYFloats) + }, + positiveZ : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(positiveZFloats) + }, + negativeZ : { + width : 1, + height : 1, + arrayBufferView : new Uint16Array(negativeZFloats) + } + }, + pixelDatatype : PixelDatatype.HALF_FLOAT, + sampler : new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.LINEAR, + magnificationFilter : TextureMagnificationFilter.LINEAR + }) + }); + + var fs = + 'uniform samplerCube u_texture;' + + 'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }'; + + var uniformMap = { + u_texture : function() { + return cubeMap; + } + }; + + if (!context.textureFloatLinear) { + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(positiveYColor.toBytes()); + } else { + Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor); + Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor); + var color = Color.add(positiveXColor, positiveYColor, positiveXColor); + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(color.toBytes()); + } + }); + + it('creates a cube map with half floating-point textures and linear filtering', function() { + if (!context.halfFloatingPointTexture) { + return; + } + + var positiveXFloats = [12902, 13926, 14541, 15360]; + var negativeXFloats = [13926, 12902, 14541, 15360]; + var positiveYFloats = [14541, 13926, 12902, 15360]; + var negativeYFloats = [12902, 14541, 13926, 15360]; + var positiveZFloats = [13926, 14541, 12902, 15360]; + var negativeZFloats = [14541, 12902, 13926, 15360]; + var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0); var negativeXColor = new Color(0.4, 0.2, 0.6, 1.0); var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0); diff --git a/Specs/Renderer/TextureSpec.js b/Specs/Renderer/TextureSpec.js index 62b13ce84d39..f3b6f13a0df1 100644 --- a/Specs/Renderer/TextureSpec.js +++ b/Specs/Renderer/TextureSpec.js @@ -189,54 +189,157 @@ defineSuite([ it('draws the expected floating-point texture color', function() { if (context.floatingPointTexture) { - var color = new Color(0.2, 0.4, 0.6, 1.0); - var floats = new Float32Array([color.red, color.green, color.blue, color.alpha]); + return; + } - texture = new Texture({ - context : context, - pixelFormat : PixelFormat.RGBA, - pixelDatatype : PixelDatatype.FLOAT, - source : { - width : 1, - height : 1, - arrayBufferView : floats - } - }); + var color = new Color(0.2, 0.4, 0.6, 1.0); + var floats = new Float32Array([color.red, color.green, color.blue, color.alpha]); - expect(texture.sizeInBytes).toEqual(16); + texture = new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.FLOAT, + source : { + width : 1, + height : 1, + arrayBufferView : floats + } + }); + expect(texture.sizeInBytes).toEqual(16); + + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap + }).contextToRender(color.toBytes()); + }); + + it('draws the expected floating-point texture color with linear filtering', function() { + if (!context.floatingPointTexture) { + return; + } + + var color0 = new Color(0.2, 0.4, 0.6, 1.0); + var color1 = new Color(0.1, 0.3, 0.5, 1.0); + var floats = new Float32Array([color0.red, color0.green, color0.blue, color0.alpha, + color1.red, color1.green, color1.blue, color1.alpha]); + + texture = new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.FLOAT, + source : { + width : 2, + height : 1, + arrayBufferView : floats + }, + sampler : new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.LINEAR, + magnificationFilter : TextureMagnificationFilter.LINEAR + }) + }); + + expect(texture.sizeInBytes).toEqual(32); + + var fs = + 'uniform sampler2D u_texture;' + + 'void main() { gl_FragColor = texture2D(u_texture, vec2(0.5, 0.0)); }'; + + if (!context.textureFloatLinear) { + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(color1.toBytes()); + } else { + Color.multiplyByScalar(color0, 1.0 - 0.5, color0); + Color.multiplyByScalar(color1, 0.5, color1); + Color.add(color0, color1, color1); expect({ context : context, fragmentShader : fs, uniformMap : uniformMap - }).contextToRender(color.toBytes()); + }).contextToRender(color1.toBytes()); } }); it('draws the expected half floating-point texture color', function() { - if (context.halfFloatingPointTexture) { - var color = new Color(0.2, 0.4, 0.6, 1.0); - var floats = new Uint16Array([12902, 13926, 14541, 15360]); + if (!context.halfFloatingPointTexture) { + return; + } - texture = new Texture({ - context : context, - pixelFormat : PixelFormat.RGBA, - pixelDatatype : PixelDatatype.HALF_FLOAT, - source : { - width : 1, - height : 1, - arrayBufferView : floats - }, - flipY : false - }); + var color = new Color(0.2, 0.4, 0.6, 1.0); + var floats = new Uint16Array([12902, 13926, 14541, 15360]); + + texture = new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.HALF_FLOAT, + source : { + width : 1, + height : 1, + arrayBufferView : floats + }, + flipY : false + }); + + expect(texture.sizeInBytes).toEqual(8); + + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap + }).contextToRender(color.toBytes()); + }); + + it('draws the expected half floating-point texture color with linear filtering', function() { + if (!context.halfFloatingPointTexture) { + return; + } + + var color0 = new Color(0.2, 0.4, 0.6, 1.0); + var color1 = new Color(0.1, 0.3, 0.5, 1.0); + var floats = new Uint16Array([12902, 13926, 14541, 15360, + 11878, 13517, 14336, 15360]); + + texture = new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.HALF_FLOAT, + source : { + width : 2, + height : 1, + arrayBufferView : floats + }, + flipY : false + }); + + expect(texture.sizeInBytes).toEqual(16); - expect(texture.sizeInBytes).toEqual(8); + var fs = + 'uniform sampler2D u_texture;' + + 'void main() { gl_FragColor = texture2D(u_texture, vec2(0.5, 0.0)); }'; + if (!context.textureHalfFloatLinear) { + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(color1.toBytes()); + } else { + Color.multiplyByScalar(color0, 1.0 - 0.5, color0); + Color.multiplyByScalar(color1, 0.5, color1); + Color.add(color0, color1, color1); expect({ context : context, fragmentShader : fs, uniformMap : uniformMap - }).contextToRender(color.toBytes()); + }).contextToRender(color1.toBytes()); } }); From 27cdea9a104858d0b3c022b7dc4841aa9b34a0a7 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 22 Aug 2018 15:41:22 -0400 Subject: [PATCH 02/26] Fix failing test. --- Specs/Renderer/TextureSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Specs/Renderer/TextureSpec.js b/Specs/Renderer/TextureSpec.js index f3b6f13a0df1..ad0bb76976b0 100644 --- a/Specs/Renderer/TextureSpec.js +++ b/Specs/Renderer/TextureSpec.js @@ -188,7 +188,7 @@ defineSuite([ }); it('draws the expected floating-point texture color', function() { - if (context.floatingPointTexture) { + if (!context.floatingPointTexture) { return; } From 53e5330d6cd4beed13f23ce71a09e877f7fc70f1 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 28 Aug 2018 12:47:24 -0400 Subject: [PATCH 03/26] Fixes #6965 --- CHANGES.md | 1 + Source/Core/Credit.js | 25 +++++++++++++++++++++---- Source/Core/IonGeocoderService.js | 4 +++- Source/Core/IonResource.js | 2 +- Source/Scene/CreditDisplay.js | 15 ++++++++++++--- Source/Scene/MapboxImageryProvider.js | 3 ++- Specs/Scene/CreditDisplaySpec.js | 16 ++++++++++++++++ 7 files changed, 56 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 63014608ddef..5d9ed32a6e4b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -42,6 +42,7 @@ Change Log * Fixed an issue where switching from 2D to 3D could cause a crash. [#6929](https://github.com/AnalyticalGraphicsInc/cesium/issues/6929) * Fixed an issue where point primitives behind the camera would appear in view. [#6904](https://github.com/AnalyticalGraphicsInc/cesium/issues/6904) * The `createGroundPolylineGeometry` web worker no longer depends on `GroundPolylinePrimitive`, making the worker smaller and potentially avoiding a hanging build in some webpack configurations. +* Fixed bug where credits weren't displaying correctly if more than one viewer was initialized [#6965](expect(https://github.com/AnalyticalGraphicsInc/cesium/issues/6965) ### 1.48 - 2018-08-01 diff --git a/Source/Core/Credit.js b/Source/Core/Credit.js index 227b39f9534d..cb80a7524e0a 100644 --- a/Source/Core/Credit.js +++ b/Source/Core/Credit.js @@ -2,12 +2,14 @@ define([ '../ThirdParty/xss', './defaultValue', './defined', - './defineProperties' + './defineProperties', + './Check' ], function( xss, defaultValue, defined, - defineProperties) { + defineProperties, + Check) { 'use strict'; var nextCreditId = 0; @@ -15,19 +17,22 @@ define([ /** * A credit contains data pertaining to how to display attributions/credits for certain content on the screen. - * @param {String} html An string representing an html code snippet (can be text only) + * @param {String} html An string representing an html code snippet * @param {Boolean} [showOnScreen=false] If true, the credit will be visible in the main credit container. Otherwise, it will appear in a popover * * @alias Credit * @constructor * - * @exception {DeveloperError} options.text, options.imageUrl, or options.link is required. + * @exception {DeveloperError} html is required. * * @example * //Create a credit with a tooltip, image and link * var credit = new Cesium.Credit(''); */ function Credit(html, showOnScreen) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('html', html); + //>>includeEnd('debug'); var id; var key = html; @@ -150,5 +155,17 @@ define([ return credit; }; + /** + * Duplicates a Credit instance. + * + * @param {Credit} [credit] The Credit to duplicate. + * @returns {Credit} A new Credit instance that is a duplicate of the one provided. (Returns undefined if the credit is undefined) + */ + Credit.clone = function(credit) { + if (defined(credit)) { + return new Credit(credit.html, credit.showOnScreen); + } + }; + return Credit; }); diff --git a/Source/Core/IonGeocoderService.js b/Source/Core/IonGeocoderService.js index c00cc988651b..0738089a2cbb 100644 --- a/Source/Core/IonGeocoderService.js +++ b/Source/Core/IonGeocoderService.js @@ -1,5 +1,6 @@ define([ './Check', + './Credit', './defaultValue', './defined', './defineProperties', @@ -9,6 +10,7 @@ define([ './Resource' ], function ( Check, + Credit, defaultValue, defined, defineProperties, @@ -44,7 +46,7 @@ define([ var defaultTokenCredit = Ion.getDefaultTokenCredit(accessToken); if (defined(defaultTokenCredit)) { - options.scene._frameState.creditDisplay.addDefaultCredit(defaultTokenCredit); + options.scene._frameState.creditDisplay.addDefaultCredit(Credit.clone(defaultTokenCredit)); } var searchEndpoint = server.getDerivedResource({ diff --git a/Source/Core/IonResource.js b/Source/Core/IonResource.js index 7d986bda140b..b0800cf4453b 100644 --- a/Source/Core/IonResource.js +++ b/Source/Core/IonResource.js @@ -143,7 +143,7 @@ define([ var credits = endpoint.attributions.map(Credit.getIonCredit); var defaultTokenCredit = Ion.getDefaultTokenCredit(endpointResource.queryParameters.access_token); if (defined(defaultTokenCredit)) { - credits.push(defaultTokenCredit); + credits.push(Credit.clone(defaultTokenCredit)); } return credits; }; diff --git a/Source/Scene/CreditDisplay.js b/Source/Scene/CreditDisplay.js index 7193aec11c05..91d9ee0877d5 100644 --- a/Source/Scene/CreditDisplay.js +++ b/Source/Scene/CreditDisplay.js @@ -315,6 +315,7 @@ define([ container.appendChild(expandLink); appendCss(); + var cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit); this._delimiter = defaultValue(delimiter, ' • '); this._screenContainer = screenContainer; @@ -328,12 +329,14 @@ define([ this._expandLink = expandLink; this._expanded = false; this._defaultCredits = []; + this._cesiumCredit = cesiumCredit; this._previousCesiumCredit = undefined; - this._currentCesiumCredit = CreditDisplay.cesiumCredit; + this._currentCesiumCredit = cesiumCredit; this._currentFrameCredits = { screenCredits : new AssociativeArray(), lightboxCredits : new AssociativeArray() }; + this._defaultCredit = undefined; this.viewport = viewport; @@ -357,7 +360,10 @@ define([ if (credit._isIon) { // If this is the an ion logo credit from the ion server // Juse use the default credit (which is identical) to avoid blinking - this._currentCesiumCredit = getDefaultCredit(); + if (!defined(this._defaultCredit)) { + this._defaultCredit = Credit.clone(getDefaultCredit()); + } + this._currentCesiumCredit = this._defaultCredit; return; } @@ -436,7 +442,10 @@ define([ currentFrameCredits.lightboxCredits.removeAll(); - this._currentCesiumCredit = CreditDisplay.cesiumCredit; + if (!Credit.equals(CreditDisplay.cesiumCredit, this._cesiumCredit)) { + this._cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit); + } + this._currentCesiumCredit = this._cesiumCredit; }; /** diff --git a/Source/Scene/MapboxImageryProvider.js b/Source/Scene/MapboxImageryProvider.js index 4c2d741d3321..1449f4bb1948 100644 --- a/Source/Scene/MapboxImageryProvider.js +++ b/Source/Scene/MapboxImageryProvider.js @@ -71,7 +71,8 @@ define([ var accessToken = MapboxApi.getAccessToken(options.accessToken); this._mapId = mapId; this._accessToken = accessToken; - this._accessTokenErrorCredit = MapboxApi.getErrorCredit(options.accessToken); + + this._accessTokenErrorCredit = Credit.clone(MapboxApi.getErrorCredit(options.accessToken)); var format = defaultValue(options.format, 'png'); if (!/\./.test(format)) { format = '.' + format; diff --git a/Specs/Scene/CreditDisplaySpec.js b/Specs/Scene/CreditDisplaySpec.js index e9d2458e8e23..22687e0ab519 100644 --- a/Specs/Scene/CreditDisplaySpec.js +++ b/Specs/Scene/CreditDisplaySpec.js @@ -73,6 +73,14 @@ defineSuite([ expect(credit1.id).toEqual(credit3.id); }); + it('credit clone works', function() { + var credit1 = new Credit('credit1'); + var credit2 = Credit.clone(credit1); + expect(credit1).toEqual(credit2); + var credit3 = Credit.clone(undefined); + expect(credit3).toBeUndefined(); + }); + it('credit display displays a credit', function() { creditDisplay = new CreditDisplay(container); var credit = new Credit('CesiumJS.org', true); @@ -367,4 +375,12 @@ defineSuite([ expect(creditDisplay._cesiumCreditContainer.childNodes.length).toBe(0); CreditDisplay.cesiumCredit = cesiumCredit; }); + + it('each credit display has a unique cesium credit', function() { + creditDisplay = new CreditDisplay(container); + var container2 = document.createElement('div'); + var creditDisplay2 = new CreditDisplay(container2); + expect(creditDisplay._currentCesiumCredit).toEqual(creditDisplay2._currentCesiumCredit); + expect(creditDisplay._currentCesiumCredit).not.toBe(creditDisplay2._currentCesiumCredit); + }); }); From c94c510fcdfd8ecbb79defc126061ab7319e7fbc Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 31 Aug 2018 12:06:49 -0400 Subject: [PATCH 04/26] add geographic limit rectangle --- .../gallery/Geographic Limit Rectangle.html | 88 ++++++++++++++++++ .../gallery/Geographic Limit Rectangle.jpg | Bin 0 -> 17738 bytes Source/Scene/Globe.js | 8 ++ Source/Scene/GlobeSurfaceShaderSet.js | 14 ++- Source/Scene/GlobeSurfaceTile.js | 2 + Source/Scene/GlobeSurfaceTileProvider.js | 42 ++++++++- Source/Shaders/GlobeFS.glsl | 13 +++ Specs/Scene/GlobeSurfaceTileProviderSpec.js | 38 +++++++- 8 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 Apps/Sandcastle/gallery/Geographic Limit Rectangle.html create mode 100644 Apps/Sandcastle/gallery/Geographic Limit Rectangle.jpg diff --git a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html b/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html new file mode 100644 index 000000000000..2ffb9f316a6f --- /dev/null +++ b/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html @@ -0,0 +1,88 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.jpg b/Apps/Sandcastle/gallery/Geographic Limit Rectangle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db87f75e3b4c91f1a2da4c871842a8bdab32a9ee GIT binary patch literal 17738 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 W&H4qAS(MhyGg`a*j}C% CesiumMath.EPSILON3; @@ -1357,7 +1395,7 @@ define([ uniformMap = combine(uniformMap, tileProvider.uniformMap); } - command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, clippingPlanes); + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, clippingPlanes, surfaceTile.clippedByBoundaries); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index cb7eb85310c4..e41323d2182b 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -51,6 +51,10 @@ uniform sampler2D u_oceanNormalMap; uniform vec2 u_lightingFadeDistance; #endif +#ifdef TILE_LIMIT_RECTANGLE +uniform vec4 u_geographicLimitRectangle; +#endif + #ifdef ENABLE_CLIPPING_PLANES uniform sampler2D u_clippingPlanes; uniform mat4 u_clippingPlanesMatrix; @@ -155,6 +159,15 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { + +#ifdef TILE_LIMIT_RECTANGLE + if (v_textureCoordinates.x < u_geographicLimitRectangle.x || u_geographicLimitRectangle.z < v_textureCoordinates.x || + v_textureCoordinates.y < u_geographicLimitRectangle.y || u_geographicLimitRectangle.w < v_textureCoordinates.y) + { + discard; + } +#endif + #ifdef ENABLE_CLIPPING_PLANES float clipDistance = clip(gl_FragCoord, u_clippingPlanes, u_clippingPlanesMatrix); #endif diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 00919c6f7c80..3a4f77648f9d 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -8,7 +8,6 @@ defineSuite([ 'Core/Ellipsoid', 'Core/EllipsoidTerrainProvider', 'Core/GeographicProjection', - 'Core/Intersect', 'Core/Rectangle', 'Core/WebMercatorProjection', 'Renderer/ContextLimits', @@ -39,7 +38,6 @@ defineSuite([ Ellipsoid, EllipsoidTerrainProvider, GeographicProjection, - Intersect, Rectangle, WebMercatorProjection, ContextLimits, @@ -116,6 +114,7 @@ defineSuite([ afterEach(function() { scene.imageryLayers.removeAll(); + scene.primitives.removeAll(); }); it('conforms to QuadtreeTileProvider interface', function() { @@ -939,4 +938,39 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('geographicLimitRectangle selectively enables rendering globe surface', function() { + expect(scene).toRender([0, 0, 0, 255]); + switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); + var result; + return updateUntilDone(scene.globe).then(function() { + expect(scene).notToRender([0, 0, 0, 255]); + expect(scene).toRenderAndCall(function(rgba) { + result = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + expect(scene).notToRender(result); + scene.camera.setView({ + destination : scene.globe.geographicLimitRectangle + }); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(scene).toRender(result); + }); + }); + + it('geographicLimitRectangle culls tiles outside the region', function() { + switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); + var unculledCommandCount; + return updateUntilDone(scene.globe).then(function() { + unculledCommandCount = scene.frameState.commandList.length; + scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(unculledCommandCount).toBeGreaterThan(scene.frameState.commandList.length); + }); + }); + }, 'WebGL'); From e1f21f3025c5b8a11d9aaa5fb3df59060c4adade Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 4 Sep 2018 11:39:13 -0400 Subject: [PATCH 05/26] Check for `sampleTerrainMostDetailed` support in terrain sandcastle example --- Apps/Sandcastle/gallery/Terrain.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain.html b/Apps/Sandcastle/gallery/Terrain.html index 5ac4107debab..730417f53e7c 100644 --- a/Apps/Sandcastle/gallery/Terrain.html +++ b/Apps/Sandcastle/gallery/Terrain.html @@ -10,10 +10,12 @@ @@ -191,6 +193,10 @@ }, 'sampleButtons'); Sandcastle.addToolbarButton('Sample Most Detailed Everest Terrain', function() { + if (!Cesium.defined(viewer.terrainProvider.availability)) { + console.log('sampleTerrainMostDetailed is not supported for the selected terrain provider'); + return; + } var terrainSamplePositions = createGrid(0.0005); Cesium.when(Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, terrainSamplePositions), sampleTerrainSuccess); lookAtMtEverest(); From b9eca2c124a88b2232ee849f023bffd3dde32e7c Mon Sep 17 00:00:00 2001 From: Mohamed marrouchi Date: Wed, 5 Sep 2018 17:39:04 +0100 Subject: [PATCH 06/26] Add support for OpenCage geocoder --- CHANGES.md | 1 + CONTRIBUTORS.md | 4 +- Source/Core/GeocoderService.js | 1 + Source/Core/OpenCageGeocoderService.js | 96 +++++++++++++++++++++++ Specs/Core/OpenCageGeocoderServiceSpec.js | 69 ++++++++++++++++ 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Source/Core/OpenCageGeocoderService.js create mode 100644 Specs/Core/OpenCageGeocoderServiceSpec.js diff --git a/CHANGES.md b/CHANGES.md index d1fb853b1431..3f6eed7dcaf4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -223,6 +223,7 @@ Change Log * Added ability to invoke `sampleTerrain` from node.js to enable offline terrain sampling * Added more ParticleSystem Sandcastle examples for rocket and comet tails and weather. [#6375](https://github.com/AnalyticalGraphicsInc/cesium/pull/6375) * Added color and scale attributes to the `ParticleSystem` class constructor. When defined the variables override startColor and endColor and startScale and endScale. [#6429](https://github.com/AnalyticalGraphicsInc/cesium/pull/6429) +* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). ##### Fixes :wrench: * Fixed bugs in `TimeIntervalCollection.removeInterval`. [#6418](https://github.com/AnalyticalGraphicsInc/cesium/pull/6418). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 37aec21b64e0..05ec845251ae 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -112,6 +112,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Jeremy Marzano](https://github.com/JeremyMarzano-ISPA/) * [Orbit Logic](http://www.orbitlogic.com) * [Roderick Green](https://github.com/roderickgreen/) +* [Hexastack](https://www.hexastack.com) + * [Mohamed Marrouchi](https://github.com/marrouchi/) ## [Individual CLA](Documentation/Contributors/CLAs/individual-cla-agi-v1.0.txt) * [Victor Berchet](https://github.com/vicb) @@ -123,7 +125,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Ayudh Das](https://github.com/ayudhDas) * [You Lu](https://github.com/YouLu) * [David Hite](https://github.com/dav3hit3) -* [Kevin Ring](https://github.com/kring) +* [Kevin Ring](https://gOpenCageithub.com/kring) * [M.Eng. René Schwarz](https://github.com/DigNative) * [Gilles Cébélieu (IGN France)](https://github.com/gcebelieu) * [Guillaume Beraudo](https://github.com/gberaudo) diff --git a/Source/Core/GeocoderService.js b/Source/Core/GeocoderService.js index 2d3109c6c99d..91e2de5a448e 100644 --- a/Source/Core/GeocoderService.js +++ b/Source/Core/GeocoderService.js @@ -18,6 +18,7 @@ define([ * * @see BingMapsGeocoderService * @see PeliasGeocoderService + * @see OpenCageGeocoderService */ function GeocoderService() { } diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js new file mode 100644 index 000000000000..4c8c25a358f7 --- /dev/null +++ b/Source/Core/OpenCageGeocoderService.js @@ -0,0 +1,96 @@ +define([ + './Cartesian3', + './Check', + './defined', + './defineProperties', + './GeocodeType', + './Rectangle', + './Resource' +], function ( + Cartesian3, + Check, + defined, + defineProperties, + GeocodeType, + Rectangle, + Resource) { + 'use strict'; + + /** + * Provides geocoding via a {@link https://opencagedata.com/|OpenCage} server. + * @alias OpenCageGeocoderService + * @constructor + * + * @param {Resource|String} url The endpoint to the OpenCage server. + * @param {String} apiKey The OpenCage API Key. + * + * @example + * // Configure a Viewer to use the OpenCage server hosted by https://geocode.earth/ + * var viewer = new Cesium.Viewer('cesiumContainer', { + * geocoder: new Cesium.OpenCageGeocoderService(new Cesium.Resource({ + * url: 'https://api.opencagedata.com/geocode/v1/', + * queryParameters: { + * key: '' + * } + * })) + * }); + */ + function OpenCageGeocoderService(url, apiKey) { + //>>includeStart('debug', pragmas.debug); + Check.defined('url', url); + Check.defined('apiKey', apiKey); + //>>includeEnd('debug'); + + this._url = Resource.createIfNeeded(url); + this._url.appendForwardSlash(); + this._url.setQueryParameters({key: apiKey}); + } + + defineProperties(OpenCageGeocoderService.prototype, { + /** + * The Resource used to access the OpenCage endpoint. + * @type {Resource} + * @memberof {OpenCageGeocoderService.prototype} + * @readonly + */ + url: { + get: function () { + return this._url; + } + } + }); + + /** + * @function + * + * @param {String} query The query to be sent to the geocoder service + * @returns {Promise} + */ + OpenCageGeocoderService.prototype.geocode = function(query) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('query', query); + //>>includeEnd('debug'); + + var resource = this._url.getDerivedResource({ + url: 'json', + queryParameters: { + q: query + } + }); + return resource.fetchJson() + .then(function (response) { + return response.results.map(function (resultObject) { + var lon = resultObject.geometry.lat; + var lat = resultObject.geometry.lng; + var destination = Cartesian3.fromDegrees(lon, lat); + + return { + displayName: resultObject.formatted, + destination: destination + }; + }); + }); + }; + + return OpenCageGeocoderService; +}); diff --git a/Specs/Core/OpenCageGeocoderServiceSpec.js b/Specs/Core/OpenCageGeocoderServiceSpec.js new file mode 100644 index 000000000000..ae15178410fb --- /dev/null +++ b/Specs/Core/OpenCageGeocoderServiceSpec.js @@ -0,0 +1,69 @@ +defineSuite([ + 'Core/OpenCageGeocoderService', + 'Core/GeocodeType', + 'Core/Cartesian3', + 'Core/Resource', + 'ThirdParty/when' + ], function( + OpenCageGeocoderService, + GeocodeType, + Cartesian3, + Resource, + when) { + 'use strict'; + + var endpoint = 'https://api.opencagedata.com/geocode/v1/'; + var apiKey = 'c2a490d593b14612aefa6ec2e6b77c47'; + + it('constructor throws without url', function() { + expect(function() { + return new OpenCageGeocoderService(undefined); + }).toThrowDeveloperError(); + }); + + it('returns geocoder results', function () { + var service = new OpenCageGeocoderService(endpoint, apiKey); + + var query = '-22.6792,+14.5272'; + var data = { + results: [{ + bounds: { + northeast: { + lat: -22.6790826, + lng: 14.5269016 + }, + southwest: { + lat: -22.6792826, + lng: 14.5267016 + } + }, + formatted: 'Beryl\'s Restaurant, Woermann St, Swakopmund, Namibia', + geometry: { + lat: -22.6795394, + lng: 14.5276006 + } + }] + }; + spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data)); + + return service.geocode(query) + .then(function(results) { + expect(results.length).toEqual(1); + expect(results[0].displayName).toEqual(data.results[0].formatted); + expect(results[0].destination).toBeInstanceOf(Cartesian3); + }); + }); + + it('returns no geocoder results if OpenCage has no results', function() { + var service = new OpenCageGeocoderService(endpoint, apiKey); + + var query = ''; + var data = { results: [] }; + spyOn(Resource.prototype, 'fetchJson').and.returnValue(when.resolve(data)); + + return service.geocode(query) + .then(function(results) { + expect(results.length).toEqual(0); + }); + }); +}); From ccef75b5e2be05b04c2a80f07534068405857aa5 Mon Sep 17 00:00:00 2001 From: Mohamed marrouchi Date: Thu, 6 Sep 2018 13:43:48 +0100 Subject: [PATCH 07/26] Add optional params + bbox + minor fixes --- CONTRIBUTORS.md | 2 +- Source/Core/OpenCageGeocoderService.js | 38 ++++++++++++++++++----- Specs/Core/OpenCageGeocoderServiceSpec.js | 8 ++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 05ec845251ae..05ea54f574a3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -125,7 +125,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Ayudh Das](https://github.com/ayudhDas) * [You Lu](https://github.com/YouLu) * [David Hite](https://github.com/dav3hit3) -* [Kevin Ring](https://gOpenCageithub.com/kring) +* [Kevin Ring](https://github.com/kring) * [M.Eng. René Schwarz](https://github.com/DigNative) * [Gilles Cébélieu (IGN France)](https://github.com/gcebelieu) * [Guillaume Beraudo](https://github.com/gberaudo) diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js index 4c8c25a358f7..0721bd5cfa54 100644 --- a/Source/Core/OpenCageGeocoderService.js +++ b/Source/Core/OpenCageGeocoderService.js @@ -1,6 +1,7 @@ define([ './Cartesian3', './Check', + './defaultValue', './defined', './defineProperties', './GeocodeType', @@ -9,6 +10,7 @@ define([ ], function ( Cartesian3, Check, + defaultValue, defined, defineProperties, GeocodeType, @@ -64,25 +66,47 @@ define([ * @function * * @param {String} query The query to be sent to the geocoder service + * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): + * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. + * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. + * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. + * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). + * @param {String} [options.jsonp] Wraps the returned JSON with a function name. + * @param {String} [options.language] An IETF format language code. + * @param {Number} [options.limit] The maximum number of results we should return. + * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. + * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. + * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. + * @param {Number} [options.no_record] When set to 1 the query contents are not logged. + * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. + * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * @returns {Promise} */ - OpenCageGeocoderService.prototype.geocode = function(query) { + OpenCageGeocoderService.prototype.geocode = function(query, params) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string('query', query); + if (defined(params)) { + Check.typeOf.object('value', params); + } //>>includeEnd('debug'); var resource = this._url.getDerivedResource({ url: 'json', - queryParameters: { - q: query - } + queryParameters: Object.assign(defaultValue(params, {}), {q: query}) }); return resource.fetchJson() .then(function (response) { return response.results.map(function (resultObject) { - var lon = resultObject.geometry.lat; - var lat = resultObject.geometry.lng; - var destination = Cartesian3.fromDegrees(lon, lat); + var destination; + var bounds = resultObject.bounds; + + if (defined(bounds)) { + destination = Rectangle.fromDegrees(bounds.southwest.lng, bounds.southwest.lat, bounds.northeast.lng, bounds.northeast.lat); + } else { + var lon = resultObject.geometry.lat; + var lat = resultObject.geometry.lng; + destination = Cartesian3.fromDegrees(lon, lat); + } return { displayName: resultObject.formatted, diff --git a/Specs/Core/OpenCageGeocoderServiceSpec.js b/Specs/Core/OpenCageGeocoderServiceSpec.js index ae15178410fb..fa5ff7a1922a 100644 --- a/Specs/Core/OpenCageGeocoderServiceSpec.js +++ b/Specs/Core/OpenCageGeocoderServiceSpec.js @@ -21,6 +21,12 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('constructor throws without API Key', function() { + expect(function() { + return new OpenCageGeocoderService(endpoint, undefined); + }).toThrowDeveloperError(); + }); + it('returns geocoder results', function () { var service = new OpenCageGeocoderService(endpoint, apiKey); @@ -50,7 +56,7 @@ defineSuite([ .then(function(results) { expect(results.length).toEqual(1); expect(results[0].displayName).toEqual(data.results[0].formatted); - expect(results[0].destination).toBeInstanceOf(Cartesian3); + expect(results[0].destination).toBeDefined(); }); }); From 569094f410e85dd75fcf0f9a85a4a3f0ca9956cb Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 6 Sep 2018 11:34:58 -0400 Subject: [PATCH 08/26] let geographic limit rectangle cross the IDL --- Source/Scene/GlobeSurfaceTileProvider.js | 24 +++++++++++++++++++-- Specs/Scene/GlobeSurfaceTileProviderSpec.js | 13 +++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 46cc91a5485b..2492c9c5ff54 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -4,6 +4,7 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', + '../Core/Cartographic', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', @@ -50,6 +51,7 @@ define([ Cartesian2, Cartesian3, Cartesian4, + Cartographic, Color, ColorGeometryInstanceAttribute, combine, @@ -517,6 +519,23 @@ define([ var boundingSphereScratch = new BoundingSphere(); var rectangleIntersectionScratch = new Rectangle(); + var splitGeographicLimitRectangleScratch = new Rectangle(); + var rectangleCenterScratch = new Cartographic(); + + // geographicLimitRectangle may span the IDL, but tiles never will. + function clipRectangleAntimeridian(tileRectangle, geographicLimitRectangle) { + if (geographicLimitRectangle.west < geographicLimitRectangle.east) { + return geographicLimitRectangle; + } + var splitRectangle = Rectangle.clone(geographicLimitRectangle, splitGeographicLimitRectangleScratch); + var tileCenter = Rectangle.center(tileRectangle, rectangleCenterScratch); + if (tileCenter.longitude > 0.0) { + splitRectangle.east = CesiumMath.PI; + } else { + splitRectangle.west = -CesiumMath.PI; + } + return splitRectangle; + } /** * Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not @@ -546,7 +565,8 @@ define([ // Check if the tile is outside the limit area in cartographic space surfaceTile.clippedByBoundaries = false; - var areaLimitIntersection = Rectangle.simpleIntersection(this.geographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch); + var clippedGeographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, this.geographicLimitRectangle); + var areaLimitIntersection = Rectangle.simpleIntersection(clippedGeographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch); if (!defined(areaLimitIntersection)) { return Visibility.NONE; } @@ -1281,7 +1301,7 @@ define([ // Convert tile limiter rectangle from cartographic to texture space using the tileRectangle. var localizedGeographicLimitRectangle = localizedGeographicLimitRectangleScratch; - var geographicLimitRectangle = tileProvider.geographicLimitRectangle; + var geographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, tileProvider.geographicLimitRectangle); var cartographicTileRectangle = tile.rectangle; var inverseTileWidth = 1.0 / cartographicTileRectangle.width; diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 3a4f77648f9d..9040a085bb84 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -973,4 +973,17 @@ defineSuite([ }); }); + it('geographicLimitRectangle may cross the antimeridian', function() { + switchViewMode(SceneMode.SCENE2D, new GeographicProjection(Ellipsoid.WGS84)); + var unculledCommandCount; + return updateUntilDone(scene.globe).then(function() { + unculledCommandCount = scene.frameState.commandList.length; + scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(179, -2, -179, -1); + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(unculledCommandCount).toBeGreaterThan(scene.frameState.commandList.length); + }); + }); + }, 'WebGL'); From 6cb37535f0b67349fd2bbf17ef28bf054e869786 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 6 Sep 2018 11:40:32 -0400 Subject: [PATCH 09/26] update CHANGES.md --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d1fb853b1431..b88931bba301 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 1.50 - 2018-10-01 + +##### Additions :tada: +* Added `geographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) + ### 1.49 - 2018-09-04 ##### Breaking Changes :mega: From 0a631b526236da5b6139f41d94915ddbf86c7eaf Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 6 Sep 2018 14:26:21 -0400 Subject: [PATCH 10/26] Hide globe depth and pick depth options from Cesium Inspector --- Source/Widgets/CesiumInspector/CesiumInspector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js index 013acb92c4a0..b537ee7ec57b 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.js +++ b/Source/Widgets/CesiumInspector/CesiumInspector.js @@ -112,7 +112,7 @@ define([ shaderCacheDisplay.className = 'cesium-cesiumInspector-shaderCache'; shaderCacheDisplay.setAttribute('data-bind', 'html: shaderCacheText'); generalSection.appendChild(shaderCacheDisplay); - +/* var globeDepth = createCheckBox('checked: globeDepth', 'Show globe depth'); generalSection.appendChild(globeDepth); @@ -120,7 +120,7 @@ define([ globeDepth.appendChild(globeDepthFrustum); generalSection.appendChild(createCheckBox('checked: pickDepth', 'Show pick depth')); - +*/ var depthFrustum = document.createElement('div'); generalSection.appendChild(depthFrustum); From 0ee10943043c4d6f935f26ddf263f3d8b58b457f Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 6 Sep 2018 10:35:23 -0400 Subject: [PATCH 11/26] Select ancestor of empty tile that can't refine --- CHANGES.md | 6 ++++++ Source/Scene/Cesium3DTilesetTraversal.js | 26 ++++++++++++++++-------- Specs/Scene/Cesium3DTilesetSpec.js | 20 ++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d1fb853b1431..f037ecf0f417 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ Change Log ========== +### 1.50 - 2018-10-01 + +##### Fixes :wrench: + +* Fixed an issue in the 3D Tiles traversal where empty tiles would be selected instead of their nearest loaded ancestors. [#7011](https://github.com/AnalyticalGraphicsInc/cesium/pull/7011) + ### 1.49 - 2018-09-04 ##### Breaking Changes :mega: diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index ef5eaff01521..a8d3c7dab268 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -156,13 +156,15 @@ define([ var childrenLength = children.length; for (var i = 0; i < childrenLength; ++i) { var child = children[i]; - if (child.contentAvailable) { - updateTile(tileset, child, frameState); - touchTile(tileset, child, frameState); - selectTile(tileset, child, frameState); - } else if (child._depth - root._depth < descendantSelectionDepth) { - // Continue traversing, but not too far - stack.push(child); + if (isVisible(child)) { + if (child.contentAvailable) { + updateTile(tileset, child, frameState); + touchTile(tileset, child, frameState); + selectTile(tileset, child, frameState); + } else if (child._depth - root._depth < descendantSelectionDepth) { + // Continue traversing, but not too far + stack.push(child); + } } } } @@ -469,11 +471,17 @@ define([ refines = updateAndPushChildren(tileset, tile, stack, frameState) && parentRefines; } + var stoppedRefining = !refines && parentRefines; + if (hasEmptyContent(tile)) { // Add empty tile just to show its debug bounding volume // If the tile has tileset content load the external tileset + // If the tile cannot refine further select its nearest loaded ancestor addEmptyTile(tileset, tile, frameState); loadTile(tileset, tile, frameState); + if (stoppedRefining) { + selectDesiredTile(tileset, tile, frameState); + } } else if (add) { // Additive tiles are always loaded and selected selectDesiredTile(tileset, tile, frameState); @@ -483,13 +491,13 @@ define([ // Always load tiles in the base traversal // Select tiles that can't refine further loadTile(tileset, tile, frameState); - if (!refines && parentRefines) { + if (stoppedRefining) { selectDesiredTile(tileset, tile, frameState); } } else { // Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded. // Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead. - if (!refines) { // eslint-disable-line + if (stoppedRefining) { // eslint-disable-line selectDesiredTile(tileset, tile, frameState); loadTile(tileset, tile, frameState); } else if (reachedSkippingThreshold(tileset, tile)) { diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 4fde0ab601c6..204ae8911bc9 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -1070,6 +1070,26 @@ defineSuite([ }); }); + it('replacement refinement - selects upwards when traversal stops at empty tile', function() { + // No children have content, but all grandchildren have content + // + // C + // E E + // C C C C + // + return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement1Url).then(function(tileset) { + tileset.root.geometricError = 90; + viewRootOnly(); + scene.camera.zoomIn(20); + scene.renderForSpecs(); + + var statistics = tileset._statistics; + expect(statistics.selected).toEqual(1); + expect(statistics.visited).toEqual(3); + expect(isSelected(tileset, tileset.root)).toBe(true); + }); + }); + it('replacement refinement - selects root when sse is not met and subtree is not refinable (1)', function() { // No children have content, but all grandchildren have content // From cd7b8b0b51113e45ab3f42c39a34906118c75bf9 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 6 Sep 2018 14:51:39 -0400 Subject: [PATCH 12/26] // --- Source/Widgets/CesiumInspector/CesiumInspector.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js index b537ee7ec57b..e7d30b42cd94 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.js +++ b/Source/Widgets/CesiumInspector/CesiumInspector.js @@ -112,15 +112,16 @@ define([ shaderCacheDisplay.className = 'cesium-cesiumInspector-shaderCache'; shaderCacheDisplay.setAttribute('data-bind', 'html: shaderCacheText'); generalSection.appendChild(shaderCacheDisplay); -/* - var globeDepth = createCheckBox('checked: globeDepth', 'Show globe depth'); - generalSection.appendChild(globeDepth); - var globeDepthFrustum = document.createElement('div'); - globeDepth.appendChild(globeDepthFrustum); + // https://github.com/AnalyticalGraphicsInc/cesium/issues/6763 + // var globeDepth = createCheckBox('checked: globeDepth', 'Show globe depth'); + // generalSection.appendChild(globeDepth); + // + // var globeDepthFrustum = document.createElement('div'); + // globeDepth.appendChild(globeDepthFrustum); + // + // generalSection.appendChild(createCheckBox('checked: pickDepth', 'Show pick depth')); - generalSection.appendChild(createCheckBox('checked: pickDepth', 'Show pick depth')); -*/ var depthFrustum = document.createElement('div'); generalSection.appendChild(depthFrustum); From 5bae8b3da948b2f1a50715d1f5cef4b9d2c3d599 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 7 Sep 2018 11:40:17 -0400 Subject: [PATCH 13/26] update doc --- Apps/Sandcastle/gallery/Geographic Limit Rectangle.html | 3 +-- Source/Scene/GlobeSurfaceShaderSet.js | 2 +- Source/Scene/GlobeSurfaceTileProvider.js | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html b/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html index 2ffb9f316a6f..9e924b35ea7d 100644 --- a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html +++ b/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html @@ -44,7 +44,6 @@ for (var i = 0; i < 10; i++) { rectangles.push(viewer.entities.add({ - name : 'EPSG 2093 bounds', rectangle : { coordinates : coffeeBeltRectangle, material : Cesium.Color.WHITE.withAlpha(0.0), @@ -65,7 +64,7 @@ }); var limited = true; -Sandcastle.addToolbarButton('enable/disable limiter', function() { +Sandcastle.addToolbarButton('Enable/Disable Limiter', function() { if (limited) { viewer.scene.globe.geographicLimitRectangle = Cesium.Rectangle.MAX_VALUE; limited = false; diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index e348ac39652b..c87e362fb671 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -86,7 +86,7 @@ define([ var geographicLimitRectangleFlag = 0; var geographicLimitRectangleDefine = ''; - if (clippedByBoundaries) {//} && frameState.mode !== SceneMode.SCENE3D) { + if (clippedByBoundaries) { geographicLimitRectangleFlag = 1; geographicLimitRectangleDefine = 'TILE_LIMIT_RECTANGLE'; } diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 2492c9c5ff54..8062d1ea2cb8 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -172,8 +172,7 @@ define([ this._clippingPlanes = undefined; /** - * A property specifying a {@link Rectangle} used to selectively prevent tiles outside a region from loading. - * For limiting terrain in scenes that use custom projections or Proj4JS projections that cause overlapping tiles. + * A property specifying a {@link Rectangle} used to selectively limit terrain and imagery rendering. * @type {Rectangle} */ this.geographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE); From 42a26b1c15188168d850b1e3d4bc58ef3f082e1b Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 7 Sep 2018 14:59:45 -0400 Subject: [PATCH 14/26] OIT look at frameState.usesLogDepth instead of existence of logDepth derived command --- Source/Scene/OIT.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Source/Scene/OIT.js b/Source/Scene/OIT.js index c089ea7eda17..b24ef4856895 100644 --- a/Source/Scene/OIT.js +++ b/Source/Scene/OIT.js @@ -537,6 +537,7 @@ define([ var j; var context = scene.context; + var useLogDepth = scene.frameState.useLogDepth; var framebuffer = passState.framebuffer; var length = commands.length; @@ -552,14 +553,14 @@ define([ for (j = 0; j < length; ++j) { command = commands[j]; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -568,14 +569,14 @@ define([ for (j = 0; j < length; ++j) { command = commands[j]; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -585,6 +586,7 @@ define([ function executeTranslucentCommandsSortedMRT(oit, scene, executeFunction, passState, commands, invertClassification) { var context = scene.context; + var useLogDepth = scene.frameState.useLogDepth; var framebuffer = passState.framebuffer; var length = commands.length; @@ -601,14 +603,14 @@ define([ for (var j = 0; j < length; ++j) { command = commands[j]; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = defined(command.derivedCommands.logDepth) ? command.derivedCommands.logDepth.command : command; + command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } From 6e84ef5ae88553d12dbe9f9ded9285e3daa306a6 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 10 Sep 2018 11:18:36 -0400 Subject: [PATCH 15/26] PR comments --- ...html => Cartographic Limit Rectangle.html} | 10 ++--- ...e.jpg => Cartographic Limit Rectangle.jpg} | Bin CHANGES.md | 2 +- Source/Scene/Globe.js | 6 +-- Source/Scene/GlobeSurfaceShaderSet.js | 12 +++--- Source/Scene/GlobeSurfaceTileProvider.js | 40 +++++++++--------- Source/Shaders/GlobeFS.glsl | 6 +-- Specs/Scene/GlobeSurfaceTileProviderSpec.js | 14 +++--- 8 files changed, 45 insertions(+), 45 deletions(-) rename Apps/Sandcastle/gallery/{Geographic Limit Rectangle.html => Cartographic Limit Rectangle.html} (84%) rename Apps/Sandcastle/gallery/{Geographic Limit Rectangle.jpg => Cartographic Limit Rectangle.jpg} (100%) diff --git a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html similarity index 84% rename from Apps/Sandcastle/gallery/Geographic Limit Rectangle.html rename to Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html index 9e924b35ea7d..d2a18ce874a7 100644 --- a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.html +++ b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html @@ -4,7 +4,7 @@ - + Cesium Demo @@ -34,9 +34,9 @@ }); // Tropics of Cancer and Capricorn -var coffeeBeltRectangle = Cesium.Rectangle.fromDegrees(-180, -23.43687, 180, 23.43687); +var coffeeBeltRectangle = Cesium.Rectangle.fromDegrees(-180.0, -23.43687, 180.0, 23.43687); -viewer.scene.globe.geographicLimitRectangle = coffeeBeltRectangle; +viewer.scene.globe.cartographicLimitRectangle = coffeeBeltRectangle; viewer.scene.skyAtmosphere.show = false; // Add rectangles to show bounds @@ -66,10 +66,10 @@ var limited = true; Sandcastle.addToolbarButton('Enable/Disable Limiter', function() { if (limited) { - viewer.scene.globe.geographicLimitRectangle = Cesium.Rectangle.MAX_VALUE; + viewer.scene.globe.cartographicLimitRectangle = Cesium.Rectangle.MAX_VALUE; limited = false; } else { - viewer.scene.globe.geographicLimitRectangle = coffeeBeltRectangle; + viewer.scene.globe.cartographicLimitRectangle = coffeeBeltRectangle; limited = true; } }); diff --git a/Apps/Sandcastle/gallery/Geographic Limit Rectangle.jpg b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.jpg similarity index 100% rename from Apps/Sandcastle/gallery/Geographic Limit Rectangle.jpg rename to Apps/Sandcastle/gallery/Cartographic Limit Rectangle.jpg diff --git a/CHANGES.md b/CHANGES.md index b88931bba301..3865aa72db61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ Change Log ### 1.50 - 2018-10-01 ##### Additions :tada: -* Added `geographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) +* Added `cartographicLimitRectangle` to `Globe`. Use this to limit terrain and imagery to a specific `Rectangle` area. [#6987](https://github.com/AnalyticalGraphicsInc/cesium/pull/6987) ### 1.49 - 2018-09-04 diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index ac8fe7a191f6..cfc30e5adbf6 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -277,12 +277,12 @@ define([ this._surface.tileProvider.clippingPlanes = value; } }, - geographicLimitRectangle : { + cartographicLimitRectangle : { get : function() { - return this._surface.tileProvider.geographicLimitRectangle; + return this._surface.tileProvider.cartographicLimitRectangle; }, set : function(value) { - this._surface.tileProvider.geographicLimitRectangle = value; + this._surface.tileProvider.cartographicLimitRectangle = value; } }, /** diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index c87e362fb671..2a8b5b24df08 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -84,11 +84,11 @@ define([ vertexLogDepthDefine = 'DISABLE_GL_POSITION_LOG_DEPTH'; } - var geographicLimitRectangleFlag = 0; - var geographicLimitRectangleDefine = ''; + var cartographicLimitRectangleFlag = 0; + var cartographicLimitRectangleDefine = ''; if (clippedByBoundaries) { - geographicLimitRectangleFlag = 1; - geographicLimitRectangleDefine = 'TILE_LIMIT_RECTANGLE'; + cartographicLimitRectangleFlag = 1; + cartographicLimitRectangleDefine = 'TILE_LIMIT_RECTANGLE'; } var sceneMode = frameState.mode; @@ -109,7 +109,7 @@ define([ (applySplit << 15) | (enableClippingPlanes << 16) | (vertexLogDepth << 17) | - (geographicLimitRectangleFlag << 18); + (cartographicLimitRectangleFlag << 18); var currentClippingShaderState = 0; if (defined(clippingPlanes)) { @@ -142,7 +142,7 @@ define([ } vs.defines.push(quantizationDefine, vertexLogDepthDefine); - fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures, geographicLimitRectangleDefine); + fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures, cartographicLimitRectangleDefine); if (applyBrightness) { fs.defines.push('APPLY_BRIGHTNESS'); diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 8062d1ea2cb8..6823dd8bd2e6 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -175,7 +175,7 @@ define([ * A property specifying a {@link Rectangle} used to selectively limit terrain and imagery rendering. * @type {Rectangle} */ - this.geographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE); + this.cartographicLimitRectangle = Rectangle.clone(Rectangle.MAX_VALUE); } defineProperties(GlobeSurfaceTileProvider.prototype, { @@ -518,15 +518,15 @@ define([ var boundingSphereScratch = new BoundingSphere(); var rectangleIntersectionScratch = new Rectangle(); - var splitGeographicLimitRectangleScratch = new Rectangle(); + var splitCartographicLimitRectangleScratch = new Rectangle(); var rectangleCenterScratch = new Cartographic(); - // geographicLimitRectangle may span the IDL, but tiles never will. - function clipRectangleAntimeridian(tileRectangle, geographicLimitRectangle) { - if (geographicLimitRectangle.west < geographicLimitRectangle.east) { - return geographicLimitRectangle; + // cartographicLimitRectangle may span the IDL, but tiles never will. + function clipRectangleAntimeridian(tileRectangle, cartographicLimitRectangle) { + if (cartographicLimitRectangle.west < cartographicLimitRectangle.east) { + return cartographicLimitRectangle; } - var splitRectangle = Rectangle.clone(geographicLimitRectangle, splitGeographicLimitRectangleScratch); + var splitRectangle = Rectangle.clone(cartographicLimitRectangle, splitCartographicLimitRectangleScratch); var tileCenter = Rectangle.center(tileRectangle, rectangleCenterScratch); if (tileCenter.longitude > 0.0) { splitRectangle.east = CesiumMath.PI; @@ -564,8 +564,8 @@ define([ // Check if the tile is outside the limit area in cartographic space surfaceTile.clippedByBoundaries = false; - var clippedGeographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, this.geographicLimitRectangle); - var areaLimitIntersection = Rectangle.simpleIntersection(clippedGeographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch); + var clippedCartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, this.cartographicLimitRectangle); + var areaLimitIntersection = Rectangle.simpleIntersection(clippedCartographicLimitRectangle, tile.rectangle, rectangleIntersectionScratch); if (!defined(areaLimitIntersection)) { return Visibility.NONE; } @@ -617,7 +617,7 @@ define([ var modifiedModelViewScratch = new Matrix4(); var modifiedModelViewProjectionScratch = new Matrix4(); var tileRectangleScratch = new Cartesian4(); - var localizedGeographicLimitRectangleScratch = new Cartesian4(); + var localizedCartographicLimitRectangleScratch = new Cartesian4(); var rtcScratch = new Cartesian3(); var centerEyeScratch = new Cartesian3(); var southwestScratch = new Cartesian3(); @@ -959,8 +959,8 @@ define([ } return frameState.context.defaultTexture; }, - u_geographicLimitRectangle : function() { - return this.properties.localizedGeographicLimitRectangle; + u_cartographicLimitRectangle : function() { + return this.properties.localizedCartographicLimitRectangle; }, u_clippingPlanesMatrix : function() { var clippingPlanes = globeSurfaceTileProvider._clippingPlanes; @@ -1012,7 +1012,7 @@ define([ clippingPlanesEdgeColor : Color.clone(Color.WHITE), clippingPlanesEdgeWidth : 0.0, - localizedGeographicLimitRectangle : new Cartesian4() + localizedCartographicLimitRectangle : new Cartesian4() } }; @@ -1299,18 +1299,18 @@ define([ uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight; // Convert tile limiter rectangle from cartographic to texture space using the tileRectangle. - var localizedGeographicLimitRectangle = localizedGeographicLimitRectangleScratch; - var geographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, tileProvider.geographicLimitRectangle); + var localizedCartographicLimitRectangle = localizedCartographicLimitRectangleScratch; + var cartographicLimitRectangle = clipRectangleAntimeridian(tile.rectangle, tileProvider.cartographicLimitRectangle); var cartographicTileRectangle = tile.rectangle; var inverseTileWidth = 1.0 / cartographicTileRectangle.width; var inverseTileHeight = 1.0 / cartographicTileRectangle.height; - localizedGeographicLimitRectangle.x = (geographicLimitRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; - localizedGeographicLimitRectangle.y = (geographicLimitRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; - localizedGeographicLimitRectangle.z = (geographicLimitRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; - localizedGeographicLimitRectangle.w = (geographicLimitRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; + localizedCartographicLimitRectangle.x = (cartographicLimitRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; + localizedCartographicLimitRectangle.y = (cartographicLimitRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; + localizedCartographicLimitRectangle.z = (cartographicLimitRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; + localizedCartographicLimitRectangle.w = (cartographicLimitRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; - Cartesian4.clone(localizedGeographicLimitRectangle, uniformMapProperties.localizedGeographicLimitRectangle); + Cartesian4.clone(localizedCartographicLimitRectangle, uniformMapProperties.localizedCartographicLimitRectangle); // For performance, use fog in the shader only when the tile is in fog. var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index e41323d2182b..fcae7dcb54c5 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -52,7 +52,7 @@ uniform vec2 u_lightingFadeDistance; #endif #ifdef TILE_LIMIT_RECTANGLE -uniform vec4 u_geographicLimitRectangle; +uniform vec4 u_cartographicLimitRectangle; #endif #ifdef ENABLE_CLIPPING_PLANES @@ -161,8 +161,8 @@ void main() { #ifdef TILE_LIMIT_RECTANGLE - if (v_textureCoordinates.x < u_geographicLimitRectangle.x || u_geographicLimitRectangle.z < v_textureCoordinates.x || - v_textureCoordinates.y < u_geographicLimitRectangle.y || u_geographicLimitRectangle.w < v_textureCoordinates.y) + if (v_textureCoordinates.x < u_cartographicLimitRectangle.x || u_cartographicLimitRectangle.z < v_textureCoordinates.x || + v_textureCoordinates.y < u_cartographicLimitRectangle.y || u_cartographicLimitRectangle.w < v_textureCoordinates.y) { discard; } diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 9040a085bb84..4a1b25930027 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -938,7 +938,7 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('geographicLimitRectangle selectively enables rendering globe surface', function() { + it('cartographicLimitRectangle selectively enables rendering globe surface', function() { expect(scene).toRender([0, 0, 0, 255]); switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); var result; @@ -948,10 +948,10 @@ defineSuite([ result = rgba; expect(rgba).not.toEqual([0, 0, 0, 255]); }); - scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); expect(scene).notToRender(result); scene.camera.setView({ - destination : scene.globe.geographicLimitRectangle + destination : scene.globe.cartographicLimitRectangle }); return updateUntilDone(scene.globe); }) @@ -960,12 +960,12 @@ defineSuite([ }); }); - it('geographicLimitRectangle culls tiles outside the region', function() { + it('cartographicLimitRectangle culls tiles outside the region', function() { switchViewMode(SceneMode.COLUMBUS_VIEW, new GeographicProjection(Ellipsoid.WGS84)); var unculledCommandCount; return updateUntilDone(scene.globe).then(function() { unculledCommandCount = scene.frameState.commandList.length; - scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(-2, -2, -1, -1); return updateUntilDone(scene.globe); }) .then(function() { @@ -973,12 +973,12 @@ defineSuite([ }); }); - it('geographicLimitRectangle may cross the antimeridian', function() { + it('cartographicLimitRectangle may cross the antimeridian', function() { switchViewMode(SceneMode.SCENE2D, new GeographicProjection(Ellipsoid.WGS84)); var unculledCommandCount; return updateUntilDone(scene.globe).then(function() { unculledCommandCount = scene.frameState.commandList.length; - scene.globe.geographicLimitRectangle = Rectangle.fromDegrees(179, -2, -179, -1); + scene.globe.cartographicLimitRectangle = Rectangle.fromDegrees(179, -2, -179, -1); return updateUntilDone(scene.globe); }) .then(function() { From 24b40c6a8999bd76721a377f67dc95ab4217ad37 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Sep 2018 11:18:55 -0400 Subject: [PATCH 16/26] Changed if else flow --- Source/Scene/Cesium3DTilesetTraversal.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index a8d3c7dab268..d01be2a539de 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -494,15 +494,13 @@ define([ if (stoppedRefining) { selectDesiredTile(tileset, tile, frameState); } - } else { - // Load tiles that are not skipped or can't refine further. In practice roughly half the tiles stay unloaded. - // Select tiles that can't refine further. If the tile doesn't have loaded content it will try to select an ancestor with loaded content instead. - if (stoppedRefining) { // eslint-disable-line - selectDesiredTile(tileset, tile, frameState); - loadTile(tileset, tile, frameState); - } else if (reachedSkippingThreshold(tileset, tile)) { - loadTile(tileset, tile, frameState); - } + } else if (stoppedRefining) { + // In skip traversal, load and select tiles that can't refine further + selectDesiredTile(tileset, tile, frameState); + loadTile(tileset, tile, frameState); + } else if (reachedSkippingThreshold(tileset, tile)) { + // In skip traversal, load tiles that aren't skipped. In practice roughly half the tiles stay unloaded. + loadTile(tileset, tile, frameState); } } From 39dee11f7363b96d7c27828e911fe48bdf3bd499 Mon Sep 17 00:00:00 2001 From: Omar Shehata Date: Mon, 10 Sep 2018 16:53:10 -0400 Subject: [PATCH 17/26] Removed extra paren --- Source/Scene/Scene.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index fa2c9f4d2baf..aa4142a27420 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -602,7 +602,7 @@ define([ * return; * } * viewer.scene.render(); - * var worldPosition = viewer.scene.pickPosition(movement.position)); + * var worldPosition = viewer.scene.pickPosition(movement.position); * }, Cesium.ScreenSpaceEventType.LEFT_CLICK); * * @type {Boolean} From c9a830b16081084d0c435ed06ea3c3d25d238b1b Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Sep 2018 19:14:33 -0400 Subject: [PATCH 18/26] Fix invert classification --- Source/Scene/OIT.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Scene/OIT.js b/Source/Scene/OIT.js index b24ef4856895..549d90c1c85d 100644 --- a/Source/Scene/OIT.js +++ b/Source/Scene/OIT.js @@ -560,7 +560,6 @@ define([ if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -576,7 +575,6 @@ define([ if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } @@ -610,7 +608,6 @@ define([ if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; - command = useLogDepth ? command.derivedCommands.logDepth.command : command; derivedCommand = (lightShadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction(derivedCommand, scene, context, passState, debugFramebuffer); } From f782c877a59a2161470246fe47cccca011d1799e Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 11 Sep 2018 11:35:40 -0400 Subject: [PATCH 19/26] Fixes #7028 --- .../Cesium3DTilesInspector.js | 64 +++++++++---------- .../CesiumInspector/CesiumInspector.css | 12 ++-- .../CesiumInspector/CesiumInspector.js | 60 ++++++++--------- Source/Widgets/Viewer/Viewer.css | 2 +- 4 files changed, 66 insertions(+), 72 deletions(-) diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js index 3da27a44c4af..c6bbfbb439cb 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js @@ -52,13 +52,25 @@ define([ element.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-visible" : inspectorVisible, "cesium-cesiumInspector-hidden" : !inspectorVisible}'); container.appendChild(element); + var panel = document.createElement('div'); + this._panel = panel; + panel.className = 'cesium-cesiumInspector-dropDown'; + element.appendChild(panel); + var tilesetPanelContents = document.createElement('div'); + tilesetPanelContents.className = 'cesium-cesiumInspector-sectionContent'; var displayPanelContents = document.createElement('div'); + displayPanelContents.className = 'cesium-cesiumInspector-sectionContent'; var updatePanelContents = document.createElement('div'); + updatePanelContents.className = 'cesium-cesiumInspector-sectionContent'; var loggingPanelContents = document.createElement('div'); + loggingPanelContents.className = 'cesium-cesiumInspector-sectionContent'; var tileDebugLabelsPanelContents = document.createElement('div'); + tileDebugLabelsPanelContents.className = 'cesium-cesiumInspector-sectionContent'; var stylePanelContents = document.createElement('div'); + stylePanelContents.className = 'cesium-cesiumInspector-sectionContent'; var optimizationPanelContents = document.createElement('div'); + optimizationPanelContents.className = 'cesium-cesiumInspector-sectionContent'; var properties = document.createElement('div'); properties.className = 'field-group'; @@ -119,23 +131,25 @@ define([ pickStatistics.setAttribute('data-bind', 'html: pickStatisticsText, visible: showPickStatistics'); loggingPanelContents.appendChild(pickStatistics); - stylePanelContents.appendChild(document.createTextNode('Color Blend Mode: ')); + var stylePanelEditor = document.createElement('div'); + stylePanelContents.appendChild(stylePanelEditor); + stylePanelEditor.appendChild(document.createTextNode('Color Blend Mode: ')); var blendDropdown = document.createElement('select'); blendDropdown.setAttribute('data-bind', 'options: colorBlendModes, ' + 'optionsText: "text", ' + 'optionsValue: "value", ' + 'value: colorBlendMode'); - stylePanelContents.appendChild(blendDropdown); + stylePanelEditor.appendChild(blendDropdown); var styleEditor = document.createElement('textarea'); styleEditor.setAttribute('data-bind', 'textInput: styleString, event: { keydown: styleEditorKeyPress }'); - stylePanelContents.className = 'cesium-cesiumInspector-styleEditor'; - stylePanelContents.appendChild(styleEditor); + stylePanelEditor.className = 'cesium-cesiumInspector-styleEditor'; + stylePanelEditor.appendChild(styleEditor); var closeStylesBtn = makeButton('compileStyle', 'Compile (Ctrl+Enter)'); - stylePanelContents.appendChild(closeStylesBtn); + stylePanelEditor.appendChild(closeStylesBtn); var errorBox = document.createElement('div'); errorBox.className = 'cesium-cesiumInspector-error'; errorBox.setAttribute('data-bind', 'text: editorError'); - stylePanelContents.appendChild(errorBox); + stylePanelEditor.appendChild(errorBox); tileDebugLabelsPanelContents.appendChild(makeCheckbox('showOnlyPickedTileDebugLabel', 'Show Picked Only')); tileDebugLabelsPanelContents.appendChild(makeCheckbox('showGeometricError', 'Geometric Error')); @@ -156,22 +170,12 @@ define([ optimizationPanelContents.appendChild(makeCheckbox('immediatelyLoadDesiredLevelOfDetail', 'Load only tiles that meet the max. SSE.')); optimizationPanelContents.appendChild(makeCheckbox('loadSiblings', 'Load siblings of visible tiles.')); - var tilesetPanel = makeSection('Tileset', 'tilesetVisible', 'toggleTileset', tilesetPanelContents); - var displayPanel = makeSection('Display', 'displayVisible', 'toggleDisplay', displayPanelContents); - var updatePanel = makeSection('Update', 'updateVisible', 'toggleUpdate', updatePanelContents); - var loggingPanel = makeSection('Logging', 'loggingVisible', 'toggleLogging', loggingPanelContents); - var tileDebugLabelsPanel = makeSection('Tile Debug Labels', 'tileDebugLabelsVisible', 'toggleTileDebugLabels', tileDebugLabelsPanelContents); - var stylePanel = makeSection('Style', 'styleVisible', 'toggleStyle', stylePanelContents); - var optimizationPanel = makeSection('Optimization', 'optimizationVisible', 'toggleOptimization', optimizationPanelContents); - - // first add and bind all the toggleable panels - element.appendChild(tilesetPanel); - element.appendChild(displayPanel); - element.appendChild(updatePanel); - element.appendChild(loggingPanel); - element.appendChild(tileDebugLabelsPanel); - element.appendChild(stylePanel); - element.appendChild(optimizationPanel); + makeSection(panel, 'Tileset', 'tilesetVisible', 'toggleTileset', tilesetPanelContents); + makeSection(panel, 'Display', 'displayVisible', 'toggleDisplay', displayPanelContents); + makeSection(panel, 'Logging', 'loggingVisible', 'toggleLogging', loggingPanelContents); + makeSection(panel, 'Tile Debug Labels', 'tileDebugLabelsVisible', 'toggleTileDebugLabels', tileDebugLabelsPanelContents); + makeSection(panel, 'Style', 'styleVisible', 'toggleStyle', stylePanelContents); + makeSection(panel, 'Optimization', 'optimizationVisible', 'toggleOptimization', optimizationPanelContents); knockout.applyBindings(viewModel, element); } @@ -221,27 +225,19 @@ define([ return destroyObject(this); }; - function makeSection(name, visibleProp, toggleProp, contents) { - var toggle = document.createElement('span'); - toggle.className = 'cesium-cesiumInspector-toggleSwitch'; - toggle.setAttribute('data-bind', 'text: ' + visibleProp + ' ? "-" : "+", click: ' + toggleProp); - + function makeSection(panel, name, visibleProp, toggleProp, contents) { var header = document.createElement('div'); header.className = 'cesium-cesiumInspector-sectionHeader'; - header.appendChild(toggle); + header.setAttribute('data-bind', 'click: ' + toggleProp); header.appendChild(document.createTextNode(name)); var section = document.createElement('div'); section.className = 'cesium-cesiumInspector-section'; - section.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : ' + visibleProp + ', "cesium-cesiumInspector-hide" : !' + visibleProp + '}'); + section.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-section-collapsed" : !' + visibleProp + '}'); + section.appendChild(header); section.appendChild(contents); - var panel = document.createElement('div'); - panel.className = 'cesium-cesiumInspector-dropDown'; - panel.appendChild(header); panel.appendChild(section); - - return panel; } function makeCheckbox(property, text) { diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.css b/Source/Widgets/CesiumInspector/CesiumInspector.css index 9ed1a45ea78d..f6202d1f2aba 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.css +++ b/Source/Widgets/CesiumInspector/CesiumInspector.css @@ -38,13 +38,11 @@ height: 17px; } -.cesium-cesiumInspector-sectionContent, -.cesium-cesiumInspector-show { +.cesium-cesiumInspector-sectionContent { max-height: 500px; } -.cesium-cesiumInspector-section-collapsed .cesium-cesiumInspector-sectionContent, -.cesium-cesiumInspector-hide { +.cesium-cesiumInspector-section-collapsed .cesium-cesiumInspector-sectionContent { max-height: 0; padding: 0 !important; overflow: hidden; @@ -122,11 +120,11 @@ .cesium-cesiumInspector-sectionHeader::before { margin-right: 5px; - content: '+'; + content: '-'; width: 1ch; display: inline-block; } .cesium-cesiumInspector-section-collapsed .cesium-cesiumInspector-sectionHeader::before { - content: '-'; -} \ No newline at end of file + content: '+'; +} diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js index e7d30b42cd94..f03f2ad0c6f9 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.js +++ b/Source/Widgets/CesiumInspector/CesiumInspector.js @@ -16,6 +16,36 @@ define([ CesiumInspectorViewModel) { 'use strict'; + function createCheckBox(checkboxBinding, labelText) { + var checkboxContainer = document.createElement('div'); + var checkboxLabel = document.createElement('label'); + var checkboxInput = document.createElement('input'); + checkboxInput.type = 'checkbox'; + checkboxInput.setAttribute('data-bind', checkboxBinding); + checkboxLabel.appendChild(checkboxInput); + checkboxLabel.appendChild(document.createTextNode(labelText)); + checkboxContainer.appendChild(checkboxLabel); + return checkboxContainer; + } + + function addSection(panel, headerText, sectionVisibleDataBinding, sectionVisibilityToggleClickEvent) { + var section = document.createElement('div'); + section.className = 'cesium-cesiumInspector-section'; + section.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-section-collapsed": !' + sectionVisibleDataBinding + ' }'); + panel.appendChild(section); + + var sectionHeader = document.createElement('h3'); + sectionHeader.className = 'cesium-cesiumInspector-sectionHeader'; + sectionHeader.appendChild(document.createTextNode(headerText)); + sectionHeader.setAttribute('data-bind', 'click: ' + sectionVisibilityToggleClickEvent); + section.appendChild(sectionHeader); + + var sectionContent = document.createElement('div'); + sectionContent.className = 'cesium-cesiumInspector-sectionContent'; + section.appendChild(sectionContent); + return sectionContent; + } + /** * Inspector widget to aid in debugging * @@ -40,36 +70,6 @@ define([ container = getElement(container); - function createCheckBox(checkboxBinding, labelText) { - var checkboxContainer = document.createElement('div'); - var checkboxLabel = document.createElement('label'); - var checkboxInput = document.createElement('input'); - checkboxInput.type = 'checkbox'; - checkboxInput.setAttribute('data-bind', checkboxBinding); - checkboxLabel.appendChild(checkboxInput); - checkboxLabel.appendChild(document.createTextNode(labelText)); - checkboxContainer.appendChild(checkboxLabel); - return checkboxContainer; - } - - function addSection(panel, headerText, sectionVisibleDataBinding, sectionVisibilityToogleClickEvent) { - var section = document.createElement('div'); - section.className = 'cesium-cesiumInspector-section'; - section.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-section-collapsed": !' + sectionVisibleDataBinding + ' }'); - panel.appendChild(section); - - var sectionHeader = document.createElement('h3'); - sectionHeader.className = 'cesium-cesiumInspector-sectionHeader'; - sectionHeader.appendChild(document.createTextNode(headerText)); - sectionHeader.setAttribute('data-bind', 'click: ' + sectionVisibilityToogleClickEvent); - section.appendChild(sectionHeader); - - var sectionContent = document.createElement('div'); - sectionContent.className = 'cesium-cesiumInspector-sectionContent'; - section.appendChild(sectionContent); - return sectionContent; - } - var performanceContainer = document.createElement('div'); var viewModel = new CesiumInspectorViewModel(scene, performanceContainer); diff --git a/Source/Widgets/Viewer/Viewer.css b/Source/Widgets/Viewer/Viewer.css index 8904dea9d882..387249d77974 100644 --- a/Source/Widgets/Viewer/Viewer.css +++ b/Source/Widgets/Viewer/Viewer.css @@ -102,7 +102,7 @@ top: 50px; right: 10px; max-height: 100%; - padding-bottom: 70px; + bottom: 70px; box-sizing: border-box; overflow: auto; } From 7de3029e93cb2f3fd3c286f71b08241faf6ea0c4 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 11 Sep 2018 11:44:32 -0400 Subject: [PATCH 20/26] Fixes #7021 --- LICENSE.md | 39 +- Source/Core/Credit.js | 6 +- Source/ThirdParty/purify.js | 1029 ++++++++++++++++++++++ Source/ThirdParty/xss.js | 1613 ----------------------------------- 4 files changed, 1050 insertions(+), 1637 deletions(-) create mode 100644 Source/ThirdParty/purify.js delete mode 100644 Source/ThirdParty/xss.js diff --git a/LICENSE.md b/LICENSE.md index abd8b643d3dc..1d04ba059c86 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -746,33 +746,30 @@ https://github.com/google/draco >License for the specific language governing permissions and limitations under >the License. -### JSXSS +### DOMPUrify -https://github.com/leizongmin/js-xss +https://github.com/cure53/DOMPurify -> Copyright (c) 2012-2017 Zongmin Lei(雷宗民) -> http://ucdok.com +>DOMPurify +>Copyright 2015 Mario Heiderich > -> The MIT License +>DOMPurify is free software; you can redistribute it and/or modify it under the +>terms of either: > -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: +>a) the Apache License Version 2.0, or +>b) the Mozilla Public License Version 2.0 > -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. +>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 > -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> 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. Tests ===== diff --git a/Source/Core/Credit.js b/Source/Core/Credit.js index 227b39f9534d..cfd1976e00a8 100644 --- a/Source/Core/Credit.js +++ b/Source/Core/Credit.js @@ -1,10 +1,10 @@ define([ - '../ThirdParty/xss', + '../ThirdParty/purify', './defaultValue', './defined', './defineProperties' ], function( - xss, + DOMPurify, defaultValue, defined, defineProperties) { @@ -94,7 +94,7 @@ define([ element: { get: function() { if (!defined(this._element)) { - var html = xss(this._html); + var html = DOMPurify.sanitize(this._html); var div = document.createElement('div'); div._creditId = this._id; diff --git a/Source/ThirdParty/purify.js b/Source/ThirdParty/purify.js new file mode 100644 index 000000000000..6254d4513288 --- /dev/null +++ b/Source/ThirdParty/purify.js @@ -0,0 +1,1029 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.DOMPurify = factory()); +}(this, (function () { 'use strict'; + +var html = ['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']; + +// SVG +var svg = ['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern']; + +var svgFilters = ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']; + +var mathMl = ['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmuliscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mpspace', 'msqrt', 'mystyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']; + +var text = ['#text']; + +var html$1 = ['accept', 'action', 'align', 'alt', 'autocomplete', 'background', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'coords', 'crossorigin', 'datetime', 'default', 'dir', 'disabled', 'download', 'enctype', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'integrity', 'ismap', 'label', 'lang', 'list', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns']; + +var svg$1 = ['accent-height', 'accumulate', 'additivive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'tabindex', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']; + +var mathMl$1 = ['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']; + +var xml = ['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']; + +/* Add properties to a lookup table */ +function addToSet(set, array) { + var l = array.length; + while (l--) { + if (typeof array[l] === 'string') { + array[l] = array[l].toLowerCase(); + } + set[array[l]] = true; + } + return set; +} + +/* Shallow clone an object */ +function clone(object) { + var newObject = {}; + var property = void 0; + for (property in object) { + if (Object.prototype.hasOwnProperty.call(object, property)) { + newObject[property] = object[property]; + } + } + return newObject; +} + +var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; // Specify template detection regex for SAFE_FOR_TEMPLATES mode +var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm; +var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; // eslint-disable-line no-useless-escape +var ARIA_ATTR = /^aria-[\-\w]+$/; // eslint-disable-line no-useless-escape +var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape +var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i; +var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var getGlobal = function getGlobal() { + return typeof window === 'undefined' ? null : window; +}; + +function createDOMPurify() { + var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); + + var DOMPurify = function DOMPurify(root) { + return createDOMPurify(root); + }; + + /** + * Version label, exposed for easier checks + * if DOMPurify is up to date or not + */ + DOMPurify.version = '1.0.8'; + + /** + * Array of elements that DOMPurify removed during sanitation. + * Empty if nothing was removed. + */ + DOMPurify.removed = []; + + if (!window || !window.document || window.document.nodeType !== 9) { + // Not running in a browser, provide a factory function + // so that you can pass your own Window + DOMPurify.isSupported = false; + + return DOMPurify; + } + + var originalDocument = window.document; + var useDOMParser = false; // See comment below + var removeTitle = false; // See comment below + + var document = window.document; + var DocumentFragment = window.DocumentFragment, + HTMLTemplateElement = window.HTMLTemplateElement, + Node = window.Node, + NodeFilter = window.NodeFilter, + _window$NamedNodeMap = window.NamedNodeMap, + NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, + Text = window.Text, + Comment = window.Comment, + DOMParser = window.DOMParser; + + // As per issue #47, the web-components registry is inherited by a + // new document created via createHTMLDocument. As per the spec + // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) + // a new empty registry is used when creating a template contents owner + // document, so we use that as our parent document to ensure nothing + // is inherited. + + if (typeof HTMLTemplateElement === 'function') { + var template = document.createElement('template'); + if (template.content && template.content.ownerDocument) { + document = template.content.ownerDocument; + } + } + + var _document = document, + implementation = _document.implementation, + createNodeIterator = _document.createNodeIterator, + getElementsByTagName = _document.getElementsByTagName, + createDocumentFragment = _document.createDocumentFragment; + var importNode = originalDocument.importNode; + + + var hooks = {}; + + /** + * Expose whether this browser supports running the full DOMPurify. + */ + DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9; + + var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR, + ERB_EXPR$$1 = ERB_EXPR, + DATA_ATTR$$1 = DATA_ATTR, + ARIA_ATTR$$1 = ARIA_ATTR, + IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA, + ATTR_WHITESPACE$$1 = ATTR_WHITESPACE; + var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI; + /** + * We consider the elements and attributes below to be safe. Ideally + * don't add any new ones but feel free to remove unwanted ones. + */ + + /* allowed element names */ + + var ALLOWED_TAGS = null; + var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(svgFilters), _toConsumableArray(mathMl), _toConsumableArray(text))); + + /* Allowed attribute names */ + var ALLOWED_ATTR = null; + var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(mathMl$1), _toConsumableArray(xml))); + + /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ + var FORBID_TAGS = null; + + /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ + var FORBID_ATTR = null; + + /* Decide if ARIA attributes are okay */ + var ALLOW_ARIA_ATTR = true; + + /* Decide if custom data attributes are okay */ + var ALLOW_DATA_ATTR = true; + + /* Decide if unknown protocols are okay */ + var ALLOW_UNKNOWN_PROTOCOLS = false; + + /* Output should be safe for jQuery's $() factory? */ + var SAFE_FOR_JQUERY = false; + + /* Output should be safe for common template engines. + * This means, DOMPurify removes data attributes, mustaches and ERB + */ + var SAFE_FOR_TEMPLATES = false; + + /* Decide if document with ... should be returned */ + var WHOLE_DOCUMENT = false; + + /* Track whether config is already set on this instance of DOMPurify. */ + var SET_CONFIG = false; + + /* Decide if all elements (e.g. style, script) must be children of + * document.body. By default, browsers might move them to document.head */ + var FORCE_BODY = false; + + /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string. + * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead + */ + var RETURN_DOM = false; + + /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */ + var RETURN_DOM_FRAGMENT = false; + + /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM + * `Node` is imported into the current `Document`. If this flag is not enabled the + * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by + * DOMPurify. */ + var RETURN_DOM_IMPORT = false; + + /* Output should be free from DOM clobbering attacks? */ + var SANITIZE_DOM = true; + + /* Keep element content when removing element? */ + var KEEP_CONTENT = true; + + /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead + * of importing it into a new Document and returning a sanitized copy */ + var IN_PLACE = false; + + /* Allow usage of profiles like html, svg and mathMl */ + var USE_PROFILES = {}; + + /* Tags to ignore content of when KEEP_CONTENT is true */ + var FORBID_CONTENTS = addToSet({}, ['audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video']); + + /* Tags that are safe for data: URIs */ + var DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image']); + + /* Attributes safe for values like "javascript:" */ + var URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']); + + /* Keep a reference to config to pass to hooks */ + var CONFIG = null; + + /* Ideally, do not touch anything below this line */ + /* ______________________________________________ */ + + var formElement = document.createElement('form'); + + /** + * _parseConfig + * + * @param {Object} cfg optional config literal + */ + // eslint-disable-next-line complexity + var _parseConfig = function _parseConfig(cfg) { + /* Shield configuration object from tampering */ + if ((typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') { + cfg = {}; + } + /* Set configuration parameters */ + ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; + ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; + FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; + FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; + USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; + ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true + ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true + ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false + SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false + SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false + WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false + RETURN_DOM = cfg.RETURN_DOM || false; // Default false + RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false + RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false + FORCE_BODY = cfg.FORCE_BODY || false; // Default false + SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true + KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true + IN_PLACE = cfg.IN_PLACE || false; // Default false + + IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1; + + if (SAFE_FOR_TEMPLATES) { + ALLOW_DATA_ATTR = false; + } + + if (RETURN_DOM_FRAGMENT) { + RETURN_DOM = true; + } + + /* Parse profile info */ + if (USE_PROFILES) { + ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(text))); + ALLOWED_ATTR = []; + if (USE_PROFILES.html === true) { + addToSet(ALLOWED_TAGS, html); + addToSet(ALLOWED_ATTR, html$1); + } + if (USE_PROFILES.svg === true) { + addToSet(ALLOWED_TAGS, svg); + addToSet(ALLOWED_ATTR, svg$1); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.svgFilters === true) { + addToSet(ALLOWED_TAGS, svgFilters); + addToSet(ALLOWED_ATTR, svg$1); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.mathMl === true) { + addToSet(ALLOWED_TAGS, mathMl); + addToSet(ALLOWED_ATTR, mathMl$1); + addToSet(ALLOWED_ATTR, xml); + } + } + + /* Merge configuration parameters */ + if (cfg.ADD_TAGS) { + if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { + ALLOWED_TAGS = clone(ALLOWED_TAGS); + } + addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); + } + if (cfg.ADD_ATTR) { + if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { + ALLOWED_ATTR = clone(ALLOWED_ATTR); + } + addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); + } + if (cfg.ADD_URI_SAFE_ATTR) { + addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); + } + + /* Add #text in case KEEP_CONTENT is set to true */ + if (KEEP_CONTENT) { + ALLOWED_TAGS['#text'] = true; + } + + /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ + if (WHOLE_DOCUMENT) { + addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); + } + + /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286 */ + if (ALLOWED_TAGS.table) { + addToSet(ALLOWED_TAGS, ['tbody']); + } + + // Prevent further manipulation of configuration. + // Not available in IE8, Safari 5, etc. + if (Object && 'freeze' in Object) { + Object.freeze(cfg); + } + + CONFIG = cfg; + }; + + /** + * _forceRemove + * + * @param {Node} node a DOM node + */ + var _forceRemove = function _forceRemove(node) { + DOMPurify.removed.push({ element: node }); + try { + node.parentNode.removeChild(node); + } catch (err) { + node.outerHTML = ''; + } + }; + + /** + * _removeAttribute + * + * @param {String} name an Attribute name + * @param {Node} node a DOM node + */ + var _removeAttribute = function _removeAttribute(name, node) { + try { + DOMPurify.removed.push({ + attribute: node.getAttributeNode(name), + from: node + }); + } catch (err) { + DOMPurify.removed.push({ + attribute: null, + from: node + }); + } + node.removeAttribute(name); + }; + + /** + * _initDocument + * + * @param {String} dirty a string of dirty markup + * @return {Document} a DOM, filled with the dirty markup + */ + var _initDocument = function _initDocument(dirty) { + /* Create a HTML document */ + var doc = void 0; + var leadingWhitespace = void 0; + + if (FORCE_BODY) { + dirty = '' + dirty; + } else { + /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ + var matches = dirty.match(/^[\s]+/); + leadingWhitespace = matches && matches[0]; + if (leadingWhitespace) { + dirty = dirty.slice(leadingWhitespace.length); + } + } + + /* Use DOMParser to workaround Firefox bug (see comment below) */ + if (useDOMParser) { + try { + doc = new DOMParser().parseFromString(dirty, 'text/html'); + } catch (err) {} + } + + /* Remove title to fix an mXSS bug in older MS Edge */ + if (removeTitle) { + addToSet(FORBID_TAGS, ['title']); + } + + /* Otherwise use createHTMLDocument, because DOMParser is unsafe in + Safari (see comment below) */ + if (!doc || !doc.documentElement) { + doc = implementation.createHTMLDocument(''); + var _doc = doc, + body = _doc.body; + + body.parentNode.removeChild(body.parentNode.firstElementChild); + body.outerHTML = dirty; + } + + if (leadingWhitespace) { + doc.body.insertBefore(document.createTextNode(leadingWhitespace), doc.body.childNodes[0] || null); + } + + /* Work on whole document or just its body */ + return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; + }; + + // Firefox uses a different parser for innerHTML rather than + // DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631) + // which means that you *must* use DOMParser, otherwise the output may + // not be safe if used in a document.write context later. + // + // So we feature detect the Firefox bug and use the DOMParser if necessary. + // + // MS Edge, in older versions, is affected by an mXSS behavior. The second + // check tests for the behavior and fixes it if necessary. + if (DOMPurify.isSupported) { + (function () { + try { + var doc = _initDocument('

'); + if (doc.querySelector('svg img')) { + useDOMParser = true; + } + } catch (err) {} + })(); + (function () { + try { + var doc = _initDocument('</title><img>'); + if (doc.querySelector('title').textContent.match(/<\/title/)) { + removeTitle = true; + } + } catch (err) {} + })(); + } + + /** + * _createIterator + * + * @param {Document} root document/fragment to create iterator for + * @return {Iterator} iterator instance + */ + var _createIterator = function _createIterator(root) { + return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () { + return NodeFilter.FILTER_ACCEPT; + }, false); + }; + + /** + * _isClobbered + * + * @param {Node} elm element to check for clobbering attacks + * @return {Boolean} true if clobbered, false if safe + */ + var _isClobbered = function _isClobbered(elm) { + if (elm instanceof Text || elm instanceof Comment) { + return false; + } + if (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function') { + return true; + } + return false; + }; + + /** + * _isNode + * + * @param {Node} obj object to check whether it's a DOM node + * @return {Boolean} true is object is a DOM node + */ + var _isNode = function _isNode(obj) { + return (typeof Node === 'undefined' ? 'undefined' : _typeof(Node)) === 'object' ? obj instanceof Node : obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string'; + }; + + /** + * _executeHook + * Execute user configurable hooks + * + * @param {String} entryPoint Name of the hook's entry point + * @param {Node} currentNode node to work on with the hook + * @param {Object} data additional hook parameters + */ + var _executeHook = function _executeHook(entryPoint, currentNode, data) { + if (!hooks[entryPoint]) { + return; + } + + hooks[entryPoint].forEach(function (hook) { + hook.call(DOMPurify, currentNode, data, CONFIG); + }); + }; + + /** + * _sanitizeElements + * + * @protect nodeName + * @protect textContent + * @protect removeChild + * + * @param {Node} currentNode to check for permission to exist + * @return {Boolean} true if node was killed, false if left alive + */ + var _sanitizeElements = function _sanitizeElements(currentNode) { + var content = void 0; + + /* Execute a hook if present */ + _executeHook('beforeSanitizeElements', currentNode, null); + + /* Check if element is clobbered or can clobber */ + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + return true; + } + + /* Now let's check the element's type and name */ + var tagName = currentNode.nodeName.toLowerCase(); + + /* Execute a hook if present */ + _executeHook('uponSanitizeElement', currentNode, { + tagName: tagName, + allowedTags: ALLOWED_TAGS + }); + + /* Remove element if anything forbids its presence */ + if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { + /* Keep content except for black-listed elements */ + if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') { + try { + currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML); + } catch (err) {} + } + _forceRemove(currentNode); + return true; + } + + /* Convert markup to cover jQuery behavior */ + if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && (!currentNode.content || !currentNode.content.firstElementChild) && /</g.test(currentNode.textContent)) { + DOMPurify.removed.push({ element: currentNode.cloneNode() }); + if (currentNode.innerHTML) { + currentNode.innerHTML = currentNode.innerHTML.replace(/</g, '<'); + } else { + currentNode.innerHTML = currentNode.textContent.replace(/</g, '<'); + } + } + + /* Sanitize element content to be template-safe */ + if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { + /* Get the element's text content */ + content = currentNode.textContent; + content = content.replace(MUSTACHE_EXPR$$1, ' '); + content = content.replace(ERB_EXPR$$1, ' '); + if (currentNode.textContent !== content) { + DOMPurify.removed.push({ element: currentNode.cloneNode() }); + currentNode.textContent = content; + } + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeElements', currentNode, null); + + return false; + }; + + /** + * _isValidAttribute + * + * @param {string} lcTag Lowercase tag name of containing element. + * @param {string} lcName Lowercase attribute name. + * @param {string} value Attribute value. + * @return {Boolean} Returns true if `value` is valid, otherwise false. + */ + var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { + /* Make sure attribute cannot clobber */ + if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { + return false; + } + + /* Sanitize attribute content to be template-safe */ + if (SAFE_FOR_TEMPLATES) { + value = value.replace(MUSTACHE_EXPR$$1, ' '); + value = value.replace(ERB_EXPR$$1, ' '); + } + + /* Allow valid data-* attributes: At least one character after "-" + (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) + XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) + We don't need to check the value; it's always URI safe. */ + if (ALLOW_DATA_ATTR && DATA_ATTR$$1.test(lcName)) { + // This attribute is safe + } else if (ALLOW_ARIA_ATTR && ARIA_ATTR$$1.test(lcName)) { + // This attribute is safe + /* Otherwise, check the name is permitted */ + } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { + return false; + + /* Check value is safe. First, is attr inert? If so, is safe */ + } else if (URI_SAFE_ATTRIBUTES[lcName]) { + // This attribute is safe + /* Check no script, data or unknown possibly unsafe URI + unless we know URI values are safe for that attribute */ + } else if (IS_ALLOWED_URI$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) { + // This attribute is safe + /* Keep image data URIs alive if src/xlink:href is allowed */ + /* Further prevent gadget XSS for dynamically built script tags */ + } else if ((lcName === 'src' || lcName === 'xlink:href') && lcTag !== 'script' && value.indexOf('data:') === 0 && DATA_URI_TAGS[lcTag]) { + // This attribute is safe + /* Allow unknown protocols: This provides support for links that + are handled by protocol handlers which may be unknown ahead of + time, e.g. fb:, spotify: */ + } else if (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) { + // This attribute is safe + /* Check for binary attributes */ + // eslint-disable-next-line no-negated-condition + } else if (!value) { + // Binary attributes are safe at this point + /* Anything else, presume unsafe, do not add it back */ + } else { + return false; + } + return true; + }; + + /** + * _sanitizeAttributes + * + * @protect attributes + * @protect nodeName + * @protect removeAttribute + * @protect setAttribute + * + * @param {Node} node to sanitize + */ + // eslint-disable-next-line complexity + var _sanitizeAttributes = function _sanitizeAttributes(currentNode) { + var attr = void 0; + var value = void 0; + var lcName = void 0; + var idAttr = void 0; + var l = void 0; + /* Execute a hook if present */ + _executeHook('beforeSanitizeAttributes', currentNode, null); + + var attributes = currentNode.attributes; + + /* Check if we have attributes; if not we might have a text node */ + + if (!attributes) { + return; + } + + var hookEvent = { + attrName: '', + attrValue: '', + keepAttr: true, + allowedAttributes: ALLOWED_ATTR + }; + l = attributes.length; + + /* Go backwards over all attributes; safely remove bad ones */ + while (l--) { + attr = attributes[l]; + var _attr = attr, + name = _attr.name, + namespaceURI = _attr.namespaceURI; + + value = attr.value.trim(); + lcName = name.toLowerCase(); + + /* Execute a hook if present */ + hookEvent.attrName = lcName; + hookEvent.attrValue = value; + hookEvent.keepAttr = true; + _executeHook('uponSanitizeAttribute', currentNode, hookEvent); + value = hookEvent.attrValue; + + /* Remove attribute */ + // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to + // remove a "name" attribute from an <img> tag that has an "id" + // attribute at the time. + if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) { + idAttr = attributes.id; + attributes = Array.prototype.slice.apply(attributes); + _removeAttribute('id', currentNode); + _removeAttribute(name, currentNode); + if (attributes.indexOf(idAttr) > l) { + currentNode.setAttribute('id', idAttr.value); + } + } else if ( + // This works around a bug in Safari, where input[type=file] + // cannot be dynamically set after type has been removed + currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) { + continue; + } else { + // This avoids a crash in Safari v9.0 with double-ids. + // The trick is to first set the id to be empty and then to + // remove the attribute + if (name === 'id') { + currentNode.setAttribute(name, ''); + } + _removeAttribute(name, currentNode); + } + + /* Did the hooks approve of the attribute? */ + if (!hookEvent.keepAttr) { + continue; + } + + /* Is `value` valid for this attribute? */ + var lcTag = currentNode.nodeName.toLowerCase(); + if (!_isValidAttribute(lcTag, lcName, value)) { + continue; + } + + /* Handle invalid data-* attribute set by try-catching it */ + try { + if (namespaceURI) { + currentNode.setAttributeNS(namespaceURI, name, value); + } else { + /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ + currentNode.setAttribute(name, value); + } + DOMPurify.removed.pop(); + } catch (err) {} + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeAttributes', currentNode, null); + }; + + /** + * _sanitizeShadowDOM + * + * @param {DocumentFragment} fragment to iterate over recursively + */ + var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { + var shadowNode = void 0; + var shadowIterator = _createIterator(fragment); + + /* Execute a hook if present */ + _executeHook('beforeSanitizeShadowDOM', fragment, null); + + while (shadowNode = shadowIterator.nextNode()) { + /* Execute a hook if present */ + _executeHook('uponSanitizeShadowNode', shadowNode, null); + + /* Sanitize tags and elements */ + if (_sanitizeElements(shadowNode)) { + continue; + } + + /* Deep shadow DOM detected */ + if (shadowNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(shadowNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(shadowNode); + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeShadowDOM', fragment, null); + }; + + /** + * Sanitize + * Public method providing core sanitation functionality + * + * @param {String|Node} dirty string or DOM node + * @param {Object} configuration object + */ + // eslint-disable-next-line complexity + DOMPurify.sanitize = function (dirty, cfg) { + var body = void 0; + var importedNode = void 0; + var currentNode = void 0; + var oldNode = void 0; + var returnNode = void 0; + /* Make sure we have a string to sanitize. + DO NOT return early, as this will return the wrong type if + the user has requested a DOM object rather than a string */ + if (!dirty) { + dirty = '<!-->'; + } + + /* Stringify, in case dirty is an object */ + if (typeof dirty !== 'string' && !_isNode(dirty)) { + // eslint-disable-next-line no-negated-condition + if (typeof dirty.toString !== 'function') { + throw new TypeError('toString is not a function'); + } else { + dirty = dirty.toString(); + if (typeof dirty !== 'string') { + throw new TypeError('dirty is not a string, aborting'); + } + } + } + + /* Check we can run. Otherwise fall back or ignore */ + if (!DOMPurify.isSupported) { + if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') { + if (typeof dirty === 'string') { + return window.toStaticHTML(dirty); + } + if (_isNode(dirty)) { + return window.toStaticHTML(dirty.outerHTML); + } + } + return dirty; + } + + /* Assign config vars */ + if (!SET_CONFIG) { + _parseConfig(cfg); + } + + /* Clean up removed elements */ + DOMPurify.removed = []; + + if (IN_PLACE) { + /* No special handling necessary for in-place sanitization */ + } else if (dirty instanceof Node) { + /* If dirty is a DOM element, append to an empty document to avoid + elements being stripped by the parser */ + body = _initDocument('<!-->'); + importedNode = body.ownerDocument.importNode(dirty, true); + if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { + /* Node is already a body, use as is */ + body = importedNode; + } else { + body.appendChild(importedNode); + } + } else { + /* Exit directly if we have nothing to do */ + if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) { + return dirty; + } + + /* Initialize the document to work on */ + body = _initDocument(dirty); + + /* Check we have a DOM node from the data */ + if (!body) { + return RETURN_DOM ? null : ''; + } + } + + /* Remove first element node (ours) if FORCE_BODY is set */ + if (body && FORCE_BODY) { + _forceRemove(body.firstChild); + } + + /* Get node iterator */ + var nodeIterator = _createIterator(IN_PLACE ? dirty : body); + + /* Now start iterating over the created document */ + while (currentNode = nodeIterator.nextNode()) { + /* Fix IE's strange behavior with manipulated textNodes #89 */ + if (currentNode.nodeType === 3 && currentNode === oldNode) { + continue; + } + + /* Sanitize tags and elements */ + if (_sanitizeElements(currentNode)) { + continue; + } + + /* Shadow DOM detected, sanitize it */ + if (currentNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(currentNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(currentNode); + + oldNode = currentNode; + } + + /* If we sanitized `dirty` in-place, return it. */ + if (IN_PLACE) { + return dirty; + } + + /* Return sanitized string or DOM */ + if (RETURN_DOM) { + if (RETURN_DOM_FRAGMENT) { + returnNode = createDocumentFragment.call(body.ownerDocument); + + while (body.firstChild) { + returnNode.appendChild(body.firstChild); + } + } else { + returnNode = body; + } + + if (RETURN_DOM_IMPORT) { + /* AdoptNode() is not used because internal state is not reset + (e.g. the past names map of a HTMLFormElement), this is safe + in theory but we would rather not risk another attack vector. + The state that is cloned by importNode() is explicitly defined + by the specs. */ + returnNode = importNode.call(originalDocument, returnNode, true); + } + + return returnNode; + } + + return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; + }; + + /** + * Public method to set the configuration once + * setConfig + * + * @param {Object} cfg configuration object + */ + DOMPurify.setConfig = function (cfg) { + _parseConfig(cfg); + SET_CONFIG = true; + }; + + /** + * Public method to remove the configuration + * clearConfig + * + */ + DOMPurify.clearConfig = function () { + CONFIG = null; + SET_CONFIG = false; + }; + + /** + * Public method to check if an attribute value is valid. + * Uses last set config, if any. Otherwise, uses config defaults. + * isValidAttribute + * + * @param {string} tag Tag name of containing element. + * @param {string} attr Attribute name. + * @param {string} value Attribute value. + * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. + */ + DOMPurify.isValidAttribute = function (tag, attr, value) { + /* Initialize shared config vars if necessary. */ + if (!CONFIG) { + _parseConfig({}); + } + var lcTag = tag.toLowerCase(); + var lcName = attr.toLowerCase(); + return _isValidAttribute(lcTag, lcName, value); + }; + + /** + * AddHook + * Public method to add DOMPurify hooks + * + * @param {String} entryPoint entry point for the hook to add + * @param {Function} hookFunction function to execute + */ + DOMPurify.addHook = function (entryPoint, hookFunction) { + if (typeof hookFunction !== 'function') { + return; + } + hooks[entryPoint] = hooks[entryPoint] || []; + hooks[entryPoint].push(hookFunction); + }; + + /** + * RemoveHook + * Public method to remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if more are present) + * + * @param {String} entryPoint entry point for the hook to remove + */ + DOMPurify.removeHook = function (entryPoint) { + if (hooks[entryPoint]) { + hooks[entryPoint].pop(); + } + }; + + /** + * RemoveHooks + * Public method to remove all DOMPurify hooks at a given entryPoint + * + * @param {String} entryPoint entry point for the hooks to remove + */ + DOMPurify.removeHooks = function (entryPoint) { + if (hooks[entryPoint]) { + hooks[entryPoint] = []; + } + }; + + /** + * RemoveAllHooks + * Public method to remove all DOMPurify hooks + * + */ + DOMPurify.removeAllHooks = function () { + hooks = {}; + }; + + return DOMPurify; +} + +var purify = createDOMPurify(); + +return purify; + +}))); +//# sourceMappingURL=purify.js.map diff --git a/Source/ThirdParty/xss.js b/Source/ThirdParty/xss.js deleted file mode 100644 index 24871d6276b4..000000000000 --- a/Source/ThirdParty/xss.js +++ /dev/null @@ -1,1613 +0,0 @@ -/** -* @license -* Copyright (c) 2012-2017 Zongmin Lei(雷宗民) <leizongmin@gmail.com> -* http://ucdok.com -* The MIT License -*/ - -// Built using: browserify lib/index.js --standalone xss > xss.js -// From: git@github.com:leizongmin/js-xss.git tag: v0.3.5 - -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.xss = f()}})(function(){var define,module,exports;return (function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}return e})()({1:[function(require,module,exports){ -/** - * 默认配置 - * - * @author 老雷<leizongmin@gmail.com> - */ - -var FilterCSS = require('cssfilter').FilterCSS; -var getDefaultCSSWhiteList = require('cssfilter').getDefaultWhiteList; -var _ = require('./util'); - -// 默认白名单 -function getDefaultWhiteList () { - return { - a: ['target', 'href', 'title'], - abbr: ['title'], - address: [], - area: ['shape', 'coords', 'href', 'alt'], - article: [], - aside: [], - audio: ['autoplay', 'controls', 'loop', 'preload', 'src'], - b: [], - bdi: ['dir'], - bdo: ['dir'], - big: [], - blockquote: ['cite'], - br: [], - caption: [], - center: [], - cite: [], - code: [], - col: ['align', 'valign', 'span', 'width'], - colgroup: ['align', 'valign', 'span', 'width'], - dd: [], - del: ['datetime'], - details: ['open'], - div: [], - dl: [], - dt: [], - em: [], - font: ['color', 'size', 'face'], - footer: [], - h1: [], - h2: [], - h3: [], - h4: [], - h5: [], - h6: [], - header: [], - hr: [], - i: [], - img: ['src', 'alt', 'title', 'width', 'height'], - ins: ['datetime'], - li: [], - mark: [], - nav: [], - ol: [], - p: [], - pre: [], - s: [], - section:[], - small: [], - span: [], - sub: [], - sup: [], - strong: [], - table: ['width', 'border', 'align', 'valign'], - tbody: ['align', 'valign'], - td: ['width', 'rowspan', 'colspan', 'align', 'valign'], - tfoot: ['align', 'valign'], - th: ['width', 'rowspan', 'colspan', 'align', 'valign'], - thead: ['align', 'valign'], - tr: ['rowspan', 'align', 'valign'], - tt: [], - u: [], - ul: [], - video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'] - }; -} - -// 默认CSS Filter -var defaultCSSFilter = new FilterCSS(); - -/** - * 匹配到标签时的处理方法 - * - * @param {String} tag - * @param {String} html - * @param {Object} options - * @return {String} - */ -function onTag (tag, html, options) { - // do nothing -} - -/** - * 匹配到不在白名单上的标签时的处理方法 - * - * @param {String} tag - * @param {String} html - * @param {Object} options - * @return {String} - */ -function onIgnoreTag (tag, html, options) { - // do nothing -} - -/** - * 匹配到标签属性时的处理方法 - * - * @param {String} tag - * @param {String} name - * @param {String} value - * @return {String} - */ -function onTagAttr (tag, name, value) { - // do nothing -} - -/** - * 匹配到不在白名单上的标签属性时的处理方法 - * - * @param {String} tag - * @param {String} name - * @param {String} value - * @return {String} - */ -function onIgnoreTagAttr (tag, name, value) { - // do nothing -} - -/** - * HTML转义 - * - * @param {String} html - */ -function escapeHtml (html) { - return html.replace(REGEXP_LT, '<').replace(REGEXP_GT, '>'); -} - -/** - * 安全的标签属性值 - * - * @param {String} tag - * @param {String} name - * @param {String} value - * @param {Object} cssFilter - * @return {String} - */ -function safeAttrValue (tag, name, value, cssFilter) { - // 转换为友好的属性值,再做判断 - value = friendlyAttrValue(value); - - if (name === 'href' || name === 'src') { - // 过滤 href 和 src 属性 - // 仅允许 http:// | https:// | mailto: | / | # 开头的地址 - value = _.trim(value); - if (value === '#') return '#'; - if (!(value.substr(0, 7) === 'http://' || - value.substr(0, 8) === 'https://' || - value.substr(0, 7) === 'mailto:' || - value.substr(0, 4) === 'tel:' || - value[0] === '#' || - value[0] === '/')) { - return ''; - } - } else if (name === 'background') { - // 过滤 background 属性 (这个xss漏洞较老了,可能已经不适用) - // javascript: - REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0; - if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) { - return ''; - } - } else if (name === 'style') { - // /*注释*/ - /*REGEXP_DEFAULT_ON_TAG_ATTR_3.lastIndex = 0; - if (REGEXP_DEFAULT_ON_TAG_ATTR_3.test(value)) { - return ''; - }*/ - // expression() - REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0; - if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) { - return ''; - } - // url() - REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0; - if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) { - REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0; - if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) { - return ''; - } - } - if (cssFilter !== false) { - cssFilter = cssFilter || defaultCSSFilter; - value = cssFilter.process(value); - } - } - - // 输出时需要转义<>" - value = escapeAttrValue(value); - return value; -} - -// 正则表达式 -var REGEXP_LT = /</g; -var REGEXP_GT = />/g; -var REGEXP_QUOTE = /"/g; -var REGEXP_QUOTE_2 = /"/g; -var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/img; -var REGEXP_ATTR_VALUE_COLON = /:?/img; -var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/img; -var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//mg; -var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig; -var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/ig; -var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//ig; -var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/ig; -var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/ig; - -/** - * 对双引号进行转义 - * - * @param {String} str - * @return {String} str - */ -function escapeQuote (str) { - return str.replace(REGEXP_QUOTE, '"'); -} - -/** - * 对双引号进行转义 - * - * @param {String} str - * @return {String} str - */ -function unescapeQuote (str) { - return str.replace(REGEXP_QUOTE_2, '"'); -} - -/** - * 对html实体编码进行转义 - * - * @param {String} str - * @return {String} - */ -function escapeHtmlEntities (str) { - return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode (str, code) { - return (code[0] === 'x' || code[0] === 'X') - ? String.fromCharCode(parseInt(code.substr(1), 16)) - : String.fromCharCode(parseInt(code, 10)); - }); -} - -/** - * 对html5新增的危险实体编码进行转义 - * - * @param {String} str - * @return {String} - */ -function escapeDangerHtml5Entities (str) { - return str.replace(REGEXP_ATTR_VALUE_COLON, ':') - .replace(REGEXP_ATTR_VALUE_NEWLINE, ' '); -} - -/** - * 清除不可见字符 - * - * @param {String} str - * @return {String} - */ -function clearNonPrintableCharacter (str) { - var str2 = ''; - for (var i = 0, len = str.length; i < len; i++) { - str2 += str.charCodeAt(i) < 32 ? ' ' : str.charAt(i); - } - return _.trim(str2); -} - -/** - * 将标签的属性值转换成一般字符,便于分析 - * - * @param {String} str - * @return {String} - */ -function friendlyAttrValue (str) { - str = unescapeQuote(str); // 双引号 - str = escapeHtmlEntities(str); // 转换HTML实体编码 - str = escapeDangerHtml5Entities(str); // 转换危险的HTML5新增实体编码 - str = clearNonPrintableCharacter(str); // 清除不可见字符 - return str; -} - -/** - * 转义用于输出的标签属性值 - * - * @param {String} str - * @return {String} - */ -function escapeAttrValue (str) { - str = escapeQuote(str); - str = escapeHtml(str); - return str; -} - -/** - * 去掉不在白名单中的标签onIgnoreTag处理方法 - */ -function onIgnoreTagStripAll () { - return ''; -} - -/** - * 删除标签体 - * - * @param {array} tags 要删除的标签列表 - * @param {function} next 对不在列表中的标签的处理函数,可选 - */ -function StripTagBody (tags, next) { - if (typeof(next) !== 'function') { - next = function () {}; - } - - var isRemoveAllTag = !Array.isArray(tags); - function isRemoveTag (tag) { - if (isRemoveAllTag) return true; - return (_.indexOf(tags, tag) !== -1); - } - - var removeList = []; // 要删除的位置范围列表 - var posStart = false; // 当前标签开始位置 - - return { - onIgnoreTag: function (tag, html, options) { - if (isRemoveTag(tag)) { - if (options.isClosing) { - var ret = '[/removed]'; - var end = options.position + ret.length; - removeList.push([posStart !== false ? posStart : options.position, end]); - posStart = false; - return ret; - } else { - if (!posStart) { - posStart = options.position; - } - return '[removed]'; - } - } else { - return next(tag, html, options); - } - }, - remove: function (html) { - var rethtml = ''; - var lastPos = 0; - _.forEach(removeList, function (pos) { - rethtml += html.slice(lastPos, pos[0]); - lastPos = pos[1]; - }); - rethtml += html.slice(lastPos); - return rethtml; - } - }; -} - -/** - * 去除备注标签 - * - * @param {String} html - * @return {String} - */ -function stripCommentTag (html) { - return html.replace(STRIP_COMMENT_TAG_REGEXP, ''); -} -var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g; - -/** - * 去除不可见字符 - * - * @param {String} html - * @return {String} - */ -function stripBlankChar (html) { - var chars = html.split(''); - chars = chars.filter(function (char) { - var c = char.charCodeAt(0); - if (c === 127) return false; - if (c <= 31) { - if (c === 10 || c === 13) return true; - return false; - } - return true; - }); - return chars.join(''); -} - - -exports.whiteList = getDefaultWhiteList(); -exports.getDefaultWhiteList = getDefaultWhiteList; -exports.onTag = onTag; -exports.onIgnoreTag = onIgnoreTag; -exports.onTagAttr = onTagAttr; -exports.onIgnoreTagAttr = onIgnoreTagAttr; -exports.safeAttrValue = safeAttrValue; -exports.escapeHtml = escapeHtml; -exports.escapeQuote = escapeQuote; -exports.unescapeQuote = unescapeQuote; -exports.escapeHtmlEntities = escapeHtmlEntities; -exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities; -exports.clearNonPrintableCharacter = clearNonPrintableCharacter; -exports.friendlyAttrValue = friendlyAttrValue; -exports.escapeAttrValue = escapeAttrValue; -exports.onIgnoreTagStripAll = onIgnoreTagStripAll; -exports.StripTagBody = StripTagBody; -exports.stripCommentTag = stripCommentTag; -exports.stripBlankChar = stripBlankChar; -exports.cssFilter = defaultCSSFilter; -exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList; - -},{"./util":4,"cssfilter":8}],2:[function(require,module,exports){ -/** - * 模块入口 - * - * @author 老雷<leizongmin@gmail.com> - */ - -var DEFAULT = require('./default'); -var parser = require('./parser'); -var FilterXSS = require('./xss'); - - -/** - * XSS过滤 - * - * @param {String} html 要过滤的HTML代码 - * @param {Object} options 选项:whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml - * @return {String} - */ -function filterXSS (html, options) { - var xss = new FilterXSS(options); - return xss.process(html); -} - - -// 输出 -exports = module.exports = filterXSS; -exports.FilterXSS = FilterXSS; -for (var i in DEFAULT) exports[i] = DEFAULT[i]; -for (var i in parser) exports[i] = parser[i]; - - -// 在浏览器端使用 -if (typeof window !== 'undefined') { - window.filterXSS = module.exports; -} - -},{"./default":1,"./parser":3,"./xss":5}],3:[function(require,module,exports){ -/** - * 简单 HTML Parser - * - * @author 老雷<leizongmin@gmail.com> - */ - -var _ = require('./util'); - -/** - * 获取标签的名称 - * - * @param {String} html 如:'<a hef="#">' - * @return {String} - */ -function getTagName (html) { - var i = _.spaceIndex(html); - if (i === -1) { - var tagName = html.slice(1, -1); - } else { - var tagName = html.slice(1, i + 1); - } - tagName = _.trim(tagName).toLowerCase(); - if (tagName.slice(0, 1) === '/') tagName = tagName.slice(1); - if (tagName.slice(-1) === '/') tagName = tagName.slice(0, -1); - return tagName; -} - -/** - * 是否为闭合标签 - * - * @param {String} html 如:'<a hef="#">' - * @return {Boolean} - */ -function isClosing (html) { - return (html.slice(0, 2) === '</'); -} - -/** - * 分析HTML代码,调用相应的函数处理,返回处理后的HTML - * - * @param {String} html - * @param {Function} onTag 处理标签的函数 - * 参数格式: function (sourcePosition, position, tag, html, isClosing) - * @param {Function} escapeHtml 对HTML进行转义的函数 - * @return {String} - */ -function parseTag (html, onTag, escapeHtml) { - 'user strict'; - - var rethtml = ''; // 待返回的HTML - var lastPos = 0; // 上一个标签结束位置 - var tagStart = false; // 当前标签开始位置 - var quoteStart = false; // 引号开始位置 - var currentPos = 0; // 当前位置 - var len = html.length; // HTML长度 - var currentHtml = ''; // 当前标签的HTML代码 - var currentTagName = ''; // 当前标签的名称 - - // 逐个分析字符 - for (currentPos = 0; currentPos < len; currentPos++) { - var c = html.charAt(currentPos); - if (tagStart === false) { - if (c === '<') { - tagStart = currentPos; - continue; - } - } else { - if (quoteStart === false) { - if (c === '<') { - rethtml += escapeHtml(html.slice(lastPos, currentPos)); - tagStart = currentPos; - lastPos = currentPos; - continue; - } - if (c === '>') { - rethtml += escapeHtml(html.slice(lastPos, tagStart)); - currentHtml = html.slice(tagStart, currentPos + 1); - currentTagName = getTagName(currentHtml); - rethtml += onTag(tagStart, - rethtml.length, - currentTagName, - currentHtml, - isClosing(currentHtml)); - lastPos = currentPos + 1; - tagStart = false; - continue; - } - // HTML标签内的引号仅当前一个字符是等于号时才有效 - if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === '=') { - quoteStart = c; - continue; - } - } else { - if (c === quoteStart) { - quoteStart = false; - continue; - } - } - } - } - if (lastPos < html.length) { - rethtml += escapeHtml(html.substr(lastPos)); - } - - return rethtml; -} - -// 不符合属性名称规则的正则表达式 -var REGEXP_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/img; - -/** - * 分析标签HTML代码,调用相应的函数处理,返回HTML - * - * @param {String} html 如标签'<a href="#" target="_blank">' 则为 'href="#" target="_blank"' - * @param {Function} onAttr 处理属性值的函数 - * 函数格式: function (name, value) - * @return {String} - */ -function parseAttr (html, onAttr) { - 'user strict'; - - var lastPos = 0; // 当前位置 - var retAttrs = []; // 待返回的属性列表 - var tmpName = false; // 临时属性名称 - var len = html.length; // HTML代码长度 - - function addAttr (name, value) { - name = _.trim(name); - name = name.replace(REGEXP_ATTR_NAME, '').toLowerCase(); - if (name.length < 1) return; - var ret = onAttr(name, value || ''); - if (ret) retAttrs.push(ret); - }; - - // 逐个分析字符 - for (var i = 0; i < len; i++) { - var c = html.charAt(i); - var v, j; - if (tmpName === false && c === '=') { - tmpName = html.slice(lastPos, i); - lastPos = i + 1; - continue; - } - if (tmpName !== false) { - // HTML标签内的引号仅当前一个字符是等于号时才有效 - if (i === lastPos && (c === '"' || c === "'") && html.charAt(i - 1) === '=') { - j = html.indexOf(c, i + 1); - if (j === -1) { - break; - } else { - v = _.trim(html.slice(lastPos + 1, j)); - addAttr(tmpName, v); - tmpName = false; - i = j; - lastPos = i + 1; - continue; - } - } - } - if (/\s|\n|\t/.test(c)) { - html = html.replace(/\s|\n|\t/g, ' '); - if (tmpName === false) { - j = findNextEqual(html, i); - if (j === -1) { - v = _.trim(html.slice(lastPos, i)); - addAttr(v); - tmpName = false; - lastPos = i + 1; - continue; - } else { - i = j - 1; - continue; - } - } else { - j = findBeforeEqual(html, i - 1); - if (j === -1) { - v = _.trim(html.slice(lastPos, i)); - v = stripQuoteWrap(v); - addAttr(tmpName, v); - tmpName = false; - lastPos = i + 1; - continue; - } else { - continue; - } - } - } - } - - if (lastPos < html.length) { - if (tmpName === false) { - addAttr(html.slice(lastPos)); - } else { - addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos)))); - } - } - - return _.trim(retAttrs.join(' ')); -} - -function findNextEqual (str, i) { - for (; i < str.length; i++) { - var c = str[i]; - if (c === ' ') continue; - if (c === '=') return i; - return -1; - } -} - -function findBeforeEqual (str, i) { - for (; i > 0; i--) { - var c = str[i]; - if (c === ' ') continue; - if (c === '=') return i; - return -1; - } -} - -function isQuoteWrapString (text) { - if ((text[0] === '"' && text[text.length - 1] === '"') || - (text[0] === '\'' && text[text.length - 1] === '\'')) { - return true; - } else { - return false; - } -}; - -function stripQuoteWrap (text) { - if (isQuoteWrapString(text)) { - return text.substr(1, text.length - 2); - } else { - return text; - } -}; - - -exports.parseTag = parseTag; -exports.parseAttr = parseAttr; - -},{"./util":4}],4:[function(require,module,exports){ -module.exports = { - indexOf: function (arr, item) { - var i, j; - if (Array.prototype.indexOf) { - return arr.indexOf(item); - } - for (i = 0, j = arr.length; i < j; i++) { - if (arr[i] === item) { - return i; - } - } - return -1; - }, - forEach: function (arr, fn, scope) { - var i, j; - if (Array.prototype.forEach) { - return arr.forEach(fn, scope); - } - for (i = 0, j = arr.length; i < j; i++) { - fn.call(scope, arr[i], i, arr); - } - }, - trim: function (str) { - if (String.prototype.trim) { - return str.trim(); - } - return str.replace(/(^\s*)|(\s*$)/g, ''); - }, - spaceIndex: function (str) { - var reg = /\s|\n|\t/; - var match = reg.exec(str); - return match ? match.index : -1; - } -}; - -},{}],5:[function(require,module,exports){ -/** - * 过滤XSS - * - * @author 老雷<leizongmin@gmail.com> - */ - -var FilterCSS = require('cssfilter').FilterCSS; -var DEFAULT = require('./default'); -var parser = require('./parser'); -var parseTag = parser.parseTag; -var parseAttr = parser.parseAttr; -var _ = require('./util'); - - -/** - * 返回值是否为空 - * - * @param {Object} obj - * @return {Boolean} - */ -function isNull (obj) { - return (obj === undefined || obj === null); -} - -/** - * 取标签内的属性列表字符串 - * - * @param {String} html - * @return {Object} - * - {String} html - * - {Boolean} closing - */ -function getAttrs (html) { - var i = _.spaceIndex(html); - if (i === -1) { - return { - html: '', - closing: (html[html.length - 2] === '/') - }; - } - html = _.trim(html.slice(i + 1, -1)); - var isClosing = (html[html.length - 1] === '/'); - if (isClosing) html = _.trim(html.slice(0, -1)); - return { - html: html, - closing: isClosing - }; -} - -/** - * 浅拷贝对象 - * - * @param {Object} obj - * @return {Object} - */ -function shallowCopyObject (obj) { - var ret = {}; - for (var i in obj) { - ret[i] = obj[i]; - } - return ret; -} - -/** - * XSS过滤对象 - * - * @param {Object} options - * 选项:whiteList, onTag, onTagAttr, onIgnoreTag, - * onIgnoreTagAttr, safeAttrValue, escapeHtml - * stripIgnoreTagBody, allowCommentTag, stripBlankChar - * css{whiteList, onAttr, onIgnoreAttr} css=false表示禁用cssfilter - */ -function FilterXSS (options) { - options = shallowCopyObject(options || {}); - - if (options.stripIgnoreTag) { - if (options.onIgnoreTag) { - console.error('Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'); - } - options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll; - } - - options.whiteList = options.whiteList || DEFAULT.whiteList; - options.onTag = options.onTag || DEFAULT.onTag; - options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr; - options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag; - options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr; - options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue; - options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml; - this.options = options; - - if (options.css === false) { - this.cssFilter = false; - } else { - options.css = options.css || {}; - this.cssFilter = new FilterCSS(options.css); - } -} - -/** - * 开始处理 - * - * @param {String} html - * @return {String} - */ -FilterXSS.prototype.process = function (html) { - // 兼容各种奇葩输入 - html = html || ''; - html = html.toString(); - if (!html) return ''; - - var me = this; - var options = me.options; - var whiteList = options.whiteList; - var onTag = options.onTag; - var onIgnoreTag = options.onIgnoreTag; - var onTagAttr = options.onTagAttr; - var onIgnoreTagAttr = options.onIgnoreTagAttr; - var safeAttrValue = options.safeAttrValue; - var escapeHtml = options.escapeHtml; - var cssFilter = me.cssFilter; - - // 是否清除不可见字符 - if (options.stripBlankChar) { - html = DEFAULT.stripBlankChar(html); - } - - // 是否禁止备注标签 - if (!options.allowCommentTag) { - html = DEFAULT.stripCommentTag(html); - } - - // 如果开启了stripIgnoreTagBody - var stripIgnoreTagBody = false; - if (options.stripIgnoreTagBody) { - var stripIgnoreTagBody = DEFAULT.StripTagBody(options.stripIgnoreTagBody, onIgnoreTag); - onIgnoreTag = stripIgnoreTagBody.onIgnoreTag; - } - - var retHtml = parseTag(html, function (sourcePosition, position, tag, html, isClosing) { - var info = { - sourcePosition: sourcePosition, - position: position, - isClosing: isClosing, - isWhite: (tag in whiteList) - }; - - // 调用onTag处理 - var ret = onTag(tag, html, info); - if (!isNull(ret)) return ret; - - // 默认标签处理方法 - if (info.isWhite) { - // 白名单标签,解析标签属性 - // 如果是闭合标签,则不需要解析属性 - if (info.isClosing) { - return '</' + tag + '>'; - } - - var attrs = getAttrs(html); - var whiteAttrList = whiteList[tag]; - var attrsHtml = parseAttr(attrs.html, function (name, value) { - - // 调用onTagAttr处理 - var isWhiteAttr = (_.indexOf(whiteAttrList, name) !== -1); - var ret = onTagAttr(tag, name, value, isWhiteAttr); - if (!isNull(ret)) return ret; - - // 默认的属性处理方法 - if (isWhiteAttr) { - // 白名单属性,调用safeAttrValue过滤属性值 - value = safeAttrValue(tag, name, value, cssFilter); - if (value) { - return name + '="' + value + '"'; - } else { - return name; - } - } else { - // 非白名单属性,调用onIgnoreTagAttr处理 - var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr); - if (!isNull(ret)) return ret; - return; - } - }); - - // 构造新的标签代码 - var html = '<' + tag; - if (attrsHtml) html += ' ' + attrsHtml; - if (attrs.closing) html += ' /'; - html += '>'; - return html; - - } else { - // 非白名单标签,调用onIgnoreTag处理 - var ret = onIgnoreTag(tag, html, info); - if (!isNull(ret)) return ret; - return escapeHtml(html); - } - - }, escapeHtml); - - // 如果开启了stripIgnoreTagBody,需要对结果再进行处理 - if (stripIgnoreTagBody) { - retHtml = stripIgnoreTagBody.remove(retHtml); - } - - return retHtml; -}; - - -module.exports = FilterXSS; - -},{"./default":1,"./parser":3,"./util":4,"cssfilter":8}],6:[function(require,module,exports){ -/** - * cssfilter - * - * @author 老雷<leizongmin@gmail.com> - */ - -var DEFAULT = require('./default'); -var parseStyle = require('./parser'); -var _ = require('./util'); - - -/** - * 返回值是否为空 - * - * @param {Object} obj - * @return {Boolean} - */ -function isNull (obj) { - return (obj === undefined || obj === null); -} - -/** - * 浅拷贝对象 - * - * @param {Object} obj - * @return {Object} - */ -function shallowCopyObject (obj) { - var ret = {}; - for (var i in obj) { - ret[i] = obj[i]; - } - return ret; -} - -/** - * 创建CSS过滤器 - * - * @param {Object} options - * - {Object} whiteList - * - {Function} onAttr - * - {Function} onIgnoreAttr - * - {Function} safeAttrValue - */ -function FilterCSS (options) { - options = shallowCopyObject(options || {}); - options.whiteList = options.whiteList || DEFAULT.whiteList; - options.onAttr = options.onAttr || DEFAULT.onAttr; - options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr; - options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue; - this.options = options; -} - -FilterCSS.prototype.process = function (css) { - // 兼容各种奇葩输入 - css = css || ''; - css = css.toString(); - if (!css) return ''; - - var me = this; - var options = me.options; - var whiteList = options.whiteList; - var onAttr = options.onAttr; - var onIgnoreAttr = options.onIgnoreAttr; - var safeAttrValue = options.safeAttrValue; - - var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) { - - var check = whiteList[name]; - var isWhite = false; - if (check === true) isWhite = check; - else if (typeof check === 'function') isWhite = check(value); - else if (check instanceof RegExp) isWhite = check.test(value); - if (isWhite !== true) isWhite = false; - - // 如果过滤后 value 为空则直接忽略 - value = safeAttrValue(name, value); - if (!value) return; - - var opts = { - position: position, - sourcePosition: sourcePosition, - source: source, - isWhite: isWhite - }; - - if (isWhite) { - - var ret = onAttr(name, value, opts); - if (isNull(ret)) { - return name + ':' + value; - } else { - return ret; - } - - } else { - - var ret = onIgnoreAttr(name, value, opts); - if (!isNull(ret)) { - return ret; - } - - } - }); - - return retCSS; -}; - - -module.exports = FilterCSS; - -},{"./default":7,"./parser":9,"./util":10}],7:[function(require,module,exports){ -/** - * cssfilter - * - * @author 老雷<leizongmin@gmail.com> - */ - -function getDefaultWhiteList () { - // 白名单值说明: - // true: 允许该属性 - // Function: function (val) { } 返回true表示允许该属性,其他值均表示不允许 - // RegExp: regexp.test(val) 返回true表示允许该属性,其他值均表示不允许 - // 除上面列出的值外均表示不允许 - var whiteList = {}; - - whiteList['align-content'] = false; // default: auto - whiteList['align-items'] = false; // default: auto - whiteList['align-self'] = false; // default: auto - whiteList['alignment-adjust'] = false; // default: auto - whiteList['alignment-baseline'] = false; // default: baseline - whiteList['all'] = false; // default: depending on individual properties - whiteList['anchor-point'] = false; // default: none - whiteList['animation'] = false; // default: depending on individual properties - whiteList['animation-delay'] = false; // default: 0 - whiteList['animation-direction'] = false; // default: normal - whiteList['animation-duration'] = false; // default: 0 - whiteList['animation-fill-mode'] = false; // default: none - whiteList['animation-iteration-count'] = false; // default: 1 - whiteList['animation-name'] = false; // default: none - whiteList['animation-play-state'] = false; // default: running - whiteList['animation-timing-function'] = false; // default: ease - whiteList['azimuth'] = false; // default: center - whiteList['backface-visibility'] = false; // default: visible - whiteList['background'] = true; // default: depending on individual properties - whiteList['background-attachment'] = true; // default: scroll - whiteList['background-clip'] = true; // default: border-box - whiteList['background-color'] = true; // default: transparent - whiteList['background-image'] = true; // default: none - whiteList['background-origin'] = true; // default: padding-box - whiteList['background-position'] = true; // default: 0% 0% - whiteList['background-repeat'] = true; // default: repeat - whiteList['background-size'] = true; // default: auto - whiteList['baseline-shift'] = false; // default: baseline - whiteList['binding'] = false; // default: none - whiteList['bleed'] = false; // default: 6pt - whiteList['bookmark-label'] = false; // default: content() - whiteList['bookmark-level'] = false; // default: none - whiteList['bookmark-state'] = false; // default: open - whiteList['border'] = true; // default: depending on individual properties - whiteList['border-bottom'] = true; // default: depending on individual properties - whiteList['border-bottom-color'] = true; // default: current color - whiteList['border-bottom-left-radius'] = true; // default: 0 - whiteList['border-bottom-right-radius'] = true; // default: 0 - whiteList['border-bottom-style'] = true; // default: none - whiteList['border-bottom-width'] = true; // default: medium - whiteList['border-collapse'] = true; // default: separate - whiteList['border-color'] = true; // default: depending on individual properties - whiteList['border-image'] = true; // default: none - whiteList['border-image-outset'] = true; // default: 0 - whiteList['border-image-repeat'] = true; // default: stretch - whiteList['border-image-slice'] = true; // default: 100% - whiteList['border-image-source'] = true; // default: none - whiteList['border-image-width'] = true; // default: 1 - whiteList['border-left'] = true; // default: depending on individual properties - whiteList['border-left-color'] = true; // default: current color - whiteList['border-left-style'] = true; // default: none - whiteList['border-left-width'] = true; // default: medium - whiteList['border-radius'] = true; // default: 0 - whiteList['border-right'] = true; // default: depending on individual properties - whiteList['border-right-color'] = true; // default: current color - whiteList['border-right-style'] = true; // default: none - whiteList['border-right-width'] = true; // default: medium - whiteList['border-spacing'] = true; // default: 0 - whiteList['border-style'] = true; // default: depending on individual properties - whiteList['border-top'] = true; // default: depending on individual properties - whiteList['border-top-color'] = true; // default: current color - whiteList['border-top-left-radius'] = true; // default: 0 - whiteList['border-top-right-radius'] = true; // default: 0 - whiteList['border-top-style'] = true; // default: none - whiteList['border-top-width'] = true; // default: medium - whiteList['border-width'] = true; // default: depending on individual properties - whiteList['bottom'] = false; // default: auto - whiteList['box-decoration-break'] = true; // default: slice - whiteList['box-shadow'] = true; // default: none - whiteList['box-sizing'] = true; // default: content-box - whiteList['box-snap'] = true; // default: none - whiteList['box-suppress'] = true; // default: show - whiteList['break-after'] = true; // default: auto - whiteList['break-before'] = true; // default: auto - whiteList['break-inside'] = true; // default: auto - whiteList['caption-side'] = false; // default: top - whiteList['chains'] = false; // default: none - whiteList['clear'] = true; // default: none - whiteList['clip'] = false; // default: auto - whiteList['clip-path'] = false; // default: none - whiteList['clip-rule'] = false; // default: nonzero - whiteList['color'] = true; // default: implementation dependent - whiteList['color-interpolation-filters'] = true; // default: auto - whiteList['column-count'] = false; // default: auto - whiteList['column-fill'] = false; // default: balance - whiteList['column-gap'] = false; // default: normal - whiteList['column-rule'] = false; // default: depending on individual properties - whiteList['column-rule-color'] = false; // default: current color - whiteList['column-rule-style'] = false; // default: medium - whiteList['column-rule-width'] = false; // default: medium - whiteList['column-span'] = false; // default: none - whiteList['column-width'] = false; // default: auto - whiteList['columns'] = false; // default: depending on individual properties - whiteList['contain'] = false; // default: none - whiteList['content'] = false; // default: normal - whiteList['counter-increment'] = false; // default: none - whiteList['counter-reset'] = false; // default: none - whiteList['counter-set'] = false; // default: none - whiteList['crop'] = false; // default: auto - whiteList['cue'] = false; // default: depending on individual properties - whiteList['cue-after'] = false; // default: none - whiteList['cue-before'] = false; // default: none - whiteList['cursor'] = false; // default: auto - whiteList['direction'] = false; // default: ltr - whiteList['display'] = true; // default: depending on individual properties - whiteList['display-inside'] = true; // default: auto - whiteList['display-list'] = true; // default: none - whiteList['display-outside'] = true; // default: inline-level - whiteList['dominant-baseline'] = false; // default: auto - whiteList['elevation'] = false; // default: level - whiteList['empty-cells'] = false; // default: show - whiteList['filter'] = false; // default: none - whiteList['flex'] = false; // default: depending on individual properties - whiteList['flex-basis'] = false; // default: auto - whiteList['flex-direction'] = false; // default: row - whiteList['flex-flow'] = false; // default: depending on individual properties - whiteList['flex-grow'] = false; // default: 0 - whiteList['flex-shrink'] = false; // default: 1 - whiteList['flex-wrap'] = false; // default: nowrap - whiteList['float'] = false; // default: none - whiteList['float-offset'] = false; // default: 0 0 - whiteList['flood-color'] = false; // default: black - whiteList['flood-opacity'] = false; // default: 1 - whiteList['flow-from'] = false; // default: none - whiteList['flow-into'] = false; // default: none - whiteList['font'] = true; // default: depending on individual properties - whiteList['font-family'] = true; // default: implementation dependent - whiteList['font-feature-settings'] = true; // default: normal - whiteList['font-kerning'] = true; // default: auto - whiteList['font-language-override'] = true; // default: normal - whiteList['font-size'] = true; // default: medium - whiteList['font-size-adjust'] = true; // default: none - whiteList['font-stretch'] = true; // default: normal - whiteList['font-style'] = true; // default: normal - whiteList['font-synthesis'] = true; // default: weight style - whiteList['font-variant'] = true; // default: normal - whiteList['font-variant-alternates'] = true; // default: normal - whiteList['font-variant-caps'] = true; // default: normal - whiteList['font-variant-east-asian'] = true; // default: normal - whiteList['font-variant-ligatures'] = true; // default: normal - whiteList['font-variant-numeric'] = true; // default: normal - whiteList['font-variant-position'] = true; // default: normal - whiteList['font-weight'] = true; // default: normal - whiteList['grid'] = false; // default: depending on individual properties - whiteList['grid-area'] = false; // default: depending on individual properties - whiteList['grid-auto-columns'] = false; // default: auto - whiteList['grid-auto-flow'] = false; // default: none - whiteList['grid-auto-rows'] = false; // default: auto - whiteList['grid-column'] = false; // default: depending on individual properties - whiteList['grid-column-end'] = false; // default: auto - whiteList['grid-column-start'] = false; // default: auto - whiteList['grid-row'] = false; // default: depending on individual properties - whiteList['grid-row-end'] = false; // default: auto - whiteList['grid-row-start'] = false; // default: auto - whiteList['grid-template'] = false; // default: depending on individual properties - whiteList['grid-template-areas'] = false; // default: none - whiteList['grid-template-columns'] = false; // default: none - whiteList['grid-template-rows'] = false; // default: none - whiteList['hanging-punctuation'] = false; // default: none - whiteList['height'] = true; // default: auto - whiteList['hyphens'] = false; // default: manual - whiteList['icon'] = false; // default: auto - whiteList['image-orientation'] = false; // default: auto - whiteList['image-resolution'] = false; // default: normal - whiteList['ime-mode'] = false; // default: auto - whiteList['initial-letters'] = false; // default: normal - whiteList['inline-box-align'] = false; // default: last - whiteList['justify-content'] = false; // default: auto - whiteList['justify-items'] = false; // default: auto - whiteList['justify-self'] = false; // default: auto - whiteList['left'] = false; // default: auto - whiteList['letter-spacing'] = true; // default: normal - whiteList['lighting-color'] = true; // default: white - whiteList['line-box-contain'] = false; // default: block inline replaced - whiteList['line-break'] = false; // default: auto - whiteList['line-grid'] = false; // default: match-parent - whiteList['line-height'] = false; // default: normal - whiteList['line-snap'] = false; // default: none - whiteList['line-stacking'] = false; // default: depending on individual properties - whiteList['line-stacking-ruby'] = false; // default: exclude-ruby - whiteList['line-stacking-shift'] = false; // default: consider-shifts - whiteList['line-stacking-strategy'] = false; // default: inline-line-height - whiteList['list-style'] = true; // default: depending on individual properties - whiteList['list-style-image'] = true; // default: none - whiteList['list-style-position'] = true; // default: outside - whiteList['list-style-type'] = true; // default: disc - whiteList['margin'] = true; // default: depending on individual properties - whiteList['margin-bottom'] = true; // default: 0 - whiteList['margin-left'] = true; // default: 0 - whiteList['margin-right'] = true; // default: 0 - whiteList['margin-top'] = true; // default: 0 - whiteList['marker-offset'] = false; // default: auto - whiteList['marker-side'] = false; // default: list-item - whiteList['marks'] = false; // default: none - whiteList['mask'] = false; // default: border-box - whiteList['mask-box'] = false; // default: see individual properties - whiteList['mask-box-outset'] = false; // default: 0 - whiteList['mask-box-repeat'] = false; // default: stretch - whiteList['mask-box-slice'] = false; // default: 0 fill - whiteList['mask-box-source'] = false; // default: none - whiteList['mask-box-width'] = false; // default: auto - whiteList['mask-clip'] = false; // default: border-box - whiteList['mask-image'] = false; // default: none - whiteList['mask-origin'] = false; // default: border-box - whiteList['mask-position'] = false; // default: center - whiteList['mask-repeat'] = false; // default: no-repeat - whiteList['mask-size'] = false; // default: border-box - whiteList['mask-source-type'] = false; // default: auto - whiteList['mask-type'] = false; // default: luminance - whiteList['max-height'] = true; // default: none - whiteList['max-lines'] = false; // default: none - whiteList['max-width'] = true; // default: none - whiteList['min-height'] = true; // default: 0 - whiteList['min-width'] = true; // default: 0 - whiteList['move-to'] = false; // default: normal - whiteList['nav-down'] = false; // default: auto - whiteList['nav-index'] = false; // default: auto - whiteList['nav-left'] = false; // default: auto - whiteList['nav-right'] = false; // default: auto - whiteList['nav-up'] = false; // default: auto - whiteList['object-fit'] = false; // default: fill - whiteList['object-position'] = false; // default: 50% 50% - whiteList['opacity'] = false; // default: 1 - whiteList['order'] = false; // default: 0 - whiteList['orphans'] = false; // default: 2 - whiteList['outline'] = false; // default: depending on individual properties - whiteList['outline-color'] = false; // default: invert - whiteList['outline-offset'] = false; // default: 0 - whiteList['outline-style'] = false; // default: none - whiteList['outline-width'] = false; // default: medium - whiteList['overflow'] = false; // default: depending on individual properties - whiteList['overflow-wrap'] = false; // default: normal - whiteList['overflow-x'] = false; // default: visible - whiteList['overflow-y'] = false; // default: visible - whiteList['padding'] = true; // default: depending on individual properties - whiteList['padding-bottom'] = true; // default: 0 - whiteList['padding-left'] = true; // default: 0 - whiteList['padding-right'] = true; // default: 0 - whiteList['padding-top'] = true; // default: 0 - whiteList['page'] = false; // default: auto - whiteList['page-break-after'] = false; // default: auto - whiteList['page-break-before'] = false; // default: auto - whiteList['page-break-inside'] = false; // default: auto - whiteList['page-policy'] = false; // default: start - whiteList['pause'] = false; // default: implementation dependent - whiteList['pause-after'] = false; // default: implementation dependent - whiteList['pause-before'] = false; // default: implementation dependent - whiteList['perspective'] = false; // default: none - whiteList['perspective-origin'] = false; // default: 50% 50% - whiteList['pitch'] = false; // default: medium - whiteList['pitch-range'] = false; // default: 50 - whiteList['play-during'] = false; // default: auto - whiteList['position'] = false; // default: static - whiteList['presentation-level'] = false; // default: 0 - whiteList['quotes'] = false; // default: text - whiteList['region-fragment'] = false; // default: auto - whiteList['resize'] = false; // default: none - whiteList['rest'] = false; // default: depending on individual properties - whiteList['rest-after'] = false; // default: none - whiteList['rest-before'] = false; // default: none - whiteList['richness'] = false; // default: 50 - whiteList['right'] = false; // default: auto - whiteList['rotation'] = false; // default: 0 - whiteList['rotation-point'] = false; // default: 50% 50% - whiteList['ruby-align'] = false; // default: auto - whiteList['ruby-merge'] = false; // default: separate - whiteList['ruby-position'] = false; // default: before - whiteList['shape-image-threshold'] = false; // default: 0.0 - whiteList['shape-outside'] = false; // default: none - whiteList['shape-margin'] = false; // default: 0 - whiteList['size'] = false; // default: auto - whiteList['speak'] = false; // default: auto - whiteList['speak-as'] = false; // default: normal - whiteList['speak-header'] = false; // default: once - whiteList['speak-numeral'] = false; // default: continuous - whiteList['speak-punctuation'] = false; // default: none - whiteList['speech-rate'] = false; // default: medium - whiteList['stress'] = false; // default: 50 - whiteList['string-set'] = false; // default: none - whiteList['tab-size'] = false; // default: 8 - whiteList['table-layout'] = false; // default: auto - whiteList['text-align'] = true; // default: start - whiteList['text-align-last'] = true; // default: auto - whiteList['text-combine-upright'] = true; // default: none - whiteList['text-decoration'] = true; // default: none - whiteList['text-decoration-color'] = true; // default: currentColor - whiteList['text-decoration-line'] = true; // default: none - whiteList['text-decoration-skip'] = true; // default: objects - whiteList['text-decoration-style'] = true; // default: solid - whiteList['text-emphasis'] = true; // default: depending on individual properties - whiteList['text-emphasis-color'] = true; // default: currentColor - whiteList['text-emphasis-position'] = true; // default: over right - whiteList['text-emphasis-style'] = true; // default: none - whiteList['text-height'] = true; // default: auto - whiteList['text-indent'] = true; // default: 0 - whiteList['text-justify'] = true; // default: auto - whiteList['text-orientation'] = true; // default: mixed - whiteList['text-overflow'] = true; // default: clip - whiteList['text-shadow'] = true; // default: none - whiteList['text-space-collapse'] = true; // default: collapse - whiteList['text-transform'] = true; // default: none - whiteList['text-underline-position'] = true; // default: auto - whiteList['text-wrap'] = true; // default: normal - whiteList['top'] = false; // default: auto - whiteList['transform'] = false; // default: none - whiteList['transform-origin'] = false; // default: 50% 50% 0 - whiteList['transform-style'] = false; // default: flat - whiteList['transition'] = false; // default: depending on individual properties - whiteList['transition-delay'] = false; // default: 0s - whiteList['transition-duration'] = false; // default: 0s - whiteList['transition-property'] = false; // default: all - whiteList['transition-timing-function'] = false; // default: ease - whiteList['unicode-bidi'] = false; // default: normal - whiteList['vertical-align'] = false; // default: baseline - whiteList['visibility'] = false; // default: visible - whiteList['voice-balance'] = false; // default: center - whiteList['voice-duration'] = false; // default: auto - whiteList['voice-family'] = false; // default: implementation dependent - whiteList['voice-pitch'] = false; // default: medium - whiteList['voice-range'] = false; // default: medium - whiteList['voice-rate'] = false; // default: normal - whiteList['voice-stress'] = false; // default: normal - whiteList['voice-volume'] = false; // default: medium - whiteList['volume'] = false; // default: medium - whiteList['white-space'] = false; // default: normal - whiteList['widows'] = false; // default: 2 - whiteList['width'] = true; // default: auto - whiteList['will-change'] = false; // default: auto - whiteList['word-break'] = true; // default: normal - whiteList['word-spacing'] = true; // default: normal - whiteList['word-wrap'] = true; // default: normal - whiteList['wrap-flow'] = false; // default: auto - whiteList['wrap-through'] = false; // default: wrap - whiteList['writing-mode'] = false; // default: horizontal-tb - whiteList['z-index'] = false; // default: auto - - return whiteList; -} - - -/** - * 匹配到白名单上的一个属性时 - * - * @param {String} name - * @param {String} value - * @param {Object} options - * @return {String} - */ -function onAttr (name, value, options) { - // do nothing -} - -/** - * 匹配到不在白名单上的一个属性时 - * - * @param {String} name - * @param {String} value - * @param {Object} options - * @return {String} - */ -function onIgnoreAttr (name, value, options) { - // do nothing -} - -var REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img; - -/** - * 过滤属性值 - * - * @param {String} name - * @param {String} value - * @return {String} - */ -function safeAttrValue(name, value) { - if (REGEXP_URL_JAVASCRIPT.test(value)) return ''; - return value; -} - - -exports.whiteList = getDefaultWhiteList(); -exports.getDefaultWhiteList = getDefaultWhiteList; -exports.onAttr = onAttr; -exports.onIgnoreAttr = onIgnoreAttr; -exports.safeAttrValue = safeAttrValue; - -},{}],8:[function(require,module,exports){ -/** - * cssfilter - * - * @author 老雷<leizongmin@gmail.com> - */ - -var DEFAULT = require('./default'); -var FilterCSS = require('./css'); - - -/** - * XSS过滤 - * - * @param {String} css 要过滤的CSS代码 - * @param {Object} options 选项:whiteList, onAttr, onIgnoreAttr - * @return {String} - */ -function filterCSS (html, options) { - var xss = new FilterCSS(options); - return xss.process(html); -} - - -// 输出 -exports = module.exports = filterCSS; -exports.FilterCSS = FilterCSS; -for (var i in DEFAULT) exports[i] = DEFAULT[i]; - -// 在浏览器端使用 -if (typeof window !== 'undefined') { - window.filterCSS = module.exports; -} - -},{"./css":6,"./default":7}],9:[function(require,module,exports){ -/** - * cssfilter - * - * @author 老雷<leizongmin@gmail.com> - */ - -var _ = require('./util'); - - -/** - * 解析style - * - * @param {String} css - * @param {Function} onAttr 处理属性的函数 - * 参数格式: function (sourcePosition, position, name, value, source) - * @return {String} - */ -function parseStyle (css, onAttr) { - css = _.trimRight(css); - if (css[css.length - 1] !== ';') css += ';'; - var cssLength = css.length; - var isParenthesisOpen = false; - var lastPos = 0; - var i = 0; - var retCSS = ''; - - function addNewAttr () { - // 如果没有正常的闭合圆括号,则直接忽略当前属性 - if (!isParenthesisOpen) { - var source = _.trim(css.slice(lastPos, i)); - var j = source.indexOf(':'); - if (j !== -1) { - var name = _.trim(source.slice(0, j)); - var value = _.trim(source.slice(j + 1)); - // 必须有属性名称 - if (name) { - var ret = onAttr(lastPos, retCSS.length, name, value, source); - if (ret) retCSS += ret + '; '; - } - } - } - lastPos = i + 1; - } - - for (; i < cssLength; i++) { - var c = css[i]; - if (c === '/' && css[i + 1] === '*') { - // 备注开始 - var j = css.indexOf('*/', i + 2); - // 如果没有正常的备注结束,则后面的部分全部跳过 - if (j === -1) break; - // 直接将当前位置调到备注结尾,并且初始化状态 - i = j + 1; - lastPos = i + 1; - isParenthesisOpen = false; - } else if (c === '(') { - isParenthesisOpen = true; - } else if (c === ')') { - isParenthesisOpen = false; - } else if (c === ';') { - if (isParenthesisOpen) { - // 在圆括号里面,忽略 - } else { - addNewAttr(); - } - } else if (c === '\n') { - addNewAttr(); - } - } - - return _.trim(retCSS); -} - -module.exports = parseStyle; - -},{"./util":10}],10:[function(require,module,exports){ -module.exports = { - indexOf: function (arr, item) { - var i, j; - if (Array.prototype.indexOf) { - return arr.indexOf(item); - } - for (i = 0, j = arr.length; i < j; i++) { - if (arr[i] === item) { - return i; - } - } - return -1; - }, - forEach: function (arr, fn, scope) { - var i, j; - if (Array.prototype.forEach) { - return arr.forEach(fn, scope); - } - for (i = 0, j = arr.length; i < j; i++) { - fn.call(scope, arr[i], i, arr); - } - }, - trim: function (str) { - if (String.prototype.trim) { - return str.trim(); - } - return str.replace(/(^\s*)|(\s*$)/g, ''); - }, - trimRight: function (str) { - if (String.prototype.trimRight) { - return str.trimRight(); - } - return str.replace(/(\s*$)/g, ''); - } -}; - -},{}]},{},[2])(2) -}); From b7a95e1a6c71edec3fdad4e115e8b991f78e7fc8 Mon Sep 17 00:00:00 2001 From: Dan Bagnell <daniel.p.bagnell@gmail.com> Date: Tue, 11 Sep 2018 15:48:45 -0400 Subject: [PATCH 21/26] Update tests from review. --- Specs/Renderer/CubeMapSpec.js | 102 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/Specs/Renderer/CubeMapSpec.js b/Specs/Renderer/CubeMapSpec.js index a917af2eb752..e4e63ae2f007 100644 --- a/Specs/Renderer/CubeMapSpec.js +++ b/Specs/Renderer/CubeMapSpec.js @@ -482,7 +482,11 @@ defineSuite([ var negativeZFloats = [14541, 12902, 13926, 15360]; var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0); + var negativeXColor = new Color(0.4, 0.2, 0.6, 1.0); var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0); + var negativeYColor = new Color(0.2, 0.6, 0.4, 1.0); + var positiveZColor = new Color(0.4, 0.6, 0.2, 1.0); + var negativeZColor = new Color(0.6, 0.2, 0.4, 1.0); cubeMap = new CubeMap({ context : context, @@ -518,43 +522,20 @@ defineSuite([ arrayBufferView : new Uint16Array(negativeZFloats) } }, - pixelDatatype : PixelDatatype.HALF_FLOAT, - sampler : new Sampler({ - wrapS : TextureWrap.CLAMP_TO_EDGE, - wrapT : TextureWrap.CLAMP_TO_EDGE, - minificationFilter : TextureMinificationFilter.LINEAR, - magnificationFilter : TextureMagnificationFilter.LINEAR - }) + pixelDatatype : PixelDatatype.HALF_FLOAT }); - var fs = - 'uniform samplerCube u_texture;' + - 'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }'; - - var uniformMap = { - u_texture : function() { - return cubeMap; - } - }; - - if (!context.textureFloatLinear) { - expect({ - context : context, - fragmentShader : fs, - uniformMap : uniformMap, - epsilon : 1 - }).contextToRender(positiveYColor.toBytes()); - } else { - Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor); - Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor); - var color = Color.add(positiveXColor, positiveYColor, positiveXColor); - expect({ - context : context, - fragmentShader : fs, - uniformMap : uniformMap, - epsilon : 1 - }).contextToRender(color.toBytes()); - } + expectCubeMapFaces({ + cubeMap : cubeMap, + expectedColors : [ + positiveXColor.toBytes(), + negativeXColor.toBytes(), + positiveYColor.toBytes(), + negativeYColor.toBytes(), + positiveZColor.toBytes(), + negativeZColor.toBytes() + ] + }); }); it('creates a cube map with half floating-point textures and linear filtering', function() { @@ -570,11 +551,7 @@ defineSuite([ var negativeZFloats = [14541, 12902, 13926, 15360]; var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0); - var negativeXColor = new Color(0.4, 0.2, 0.6, 1.0); var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0); - var negativeYColor = new Color(0.2, 0.6, 0.4, 1.0); - var positiveZColor = new Color(0.4, 0.6, 0.2, 1.0); - var negativeZColor = new Color(0.6, 0.2, 0.4, 1.0); cubeMap = new CubeMap({ context : context, @@ -610,20 +587,43 @@ defineSuite([ arrayBufferView : new Uint16Array(negativeZFloats) } }, - pixelDatatype : PixelDatatype.HALF_FLOAT + pixelDatatype : PixelDatatype.HALF_FLOAT, + sampler : new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.LINEAR, + magnificationFilter : TextureMagnificationFilter.LINEAR + }) }); - expectCubeMapFaces({ - cubeMap : cubeMap, - expectedColors : [ - positiveXColor.toBytes(), - negativeXColor.toBytes(), - positiveYColor.toBytes(), - negativeYColor.toBytes(), - positiveZColor.toBytes(), - negativeZColor.toBytes() - ] - }); + var fs = + 'uniform samplerCube u_texture;' + + 'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }'; + + var uniformMap = { + u_texture : function() { + return cubeMap; + } + }; + + if (!context.textureHalfFloatLinear) { + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(positiveYColor.toBytes()); + } else { + Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor); + Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor); + var color = Color.add(positiveXColor, positiveYColor, positiveXColor); + expect({ + context : context, + fragmentShader : fs, + uniformMap : uniformMap, + epsilon : 1 + }).contextToRender(color.toBytes()); + } }); it('creates a cube map with typed arrays and images', function() { From 75c731bbac043870d7d5e23474e8882ca4c8f0d0 Mon Sep 17 00:00:00 2001 From: hpinkos <hannah@cesium.com> Date: Tue, 11 Sep 2018 15:52:13 -0400 Subject: [PATCH 22/26] styling cleanup --- .../Cesium3DTilesInspector.css | 35 ------------------- Source/Widgets/Viewer/Viewer.css | 6 ++-- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css index 196038b4a88e..d72e80b56daf 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css @@ -35,41 +35,6 @@ ul.cesium-cesiumInspector-statistics + ul.cesium-cesiumInspector-statistics { .cesium-cesiumInspector-slider input[type=range] { margin-left: 5px; vertical-align: middle; - -webkit-appearance: none; - background: #ddd; - height: 3px; -} - -input[type=range]:focus { - outline: none; -} - -.cesium-cesiumInspector-slider input[type=range]::-webkit-slider-thumb { - -webkit-appearance: none; - border: 1px solid #000000; - height: 10px; - width: 10px; - border-radius: 5px; - background: #ffffff; - cursor: pointer; -} - -.cesium-cesiumInspector-slider input[type=range]::-moz-range-thumb { - border: 1px solid #000000; - height: 10px; - width: 10px; - border-radius: 5px; - background: #ffffff; - cursor: pointer; -} - -.cesium-cesiumInspector-slider input[type=range]::-moz-range-thumb { - border: 1px solid #000000; - height: 10px; - width: 10px; - border-radius: 5px; - background: #ffffff; - cursor: pointer; } .cesium-cesiumInspector-hide .cesium-cesiumInspector-styleEditor { diff --git a/Source/Widgets/Viewer/Viewer.css b/Source/Widgets/Viewer/Viewer.css index 387249d77974..ff9ab1cfaf5c 100644 --- a/Source/Widgets/Viewer/Viewer.css +++ b/Source/Widgets/Viewer/Viewer.css @@ -101,8 +101,8 @@ position: absolute; top: 50px; right: 10px; - max-height: 100%; - bottom: 70px; + max-height: calc(100% - 120px); box-sizing: border-box; - overflow: auto; + overflow-y: auto; + overflow-x: hidden; } From 706e728f865196c998719c412f43122c00b5c4d1 Mon Sep 17 00:00:00 2001 From: Dan Bagnell <daniel.p.bagnell@gmail.com> Date: Tue, 11 Sep 2018 16:11:55 -0400 Subject: [PATCH 23/26] Linear filtering for half-float textures is required by the OES_texture_half_float_linear extension in WebGL1 but by the OES_texture_float_linear extension in WebGL 2. --- Source/Renderer/Context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 8090b79513fe..2f2809f517d0 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -615,7 +615,7 @@ define([ */ textureHalfFloatLinear : { get : function() { - return this._textureHalfFloatLinear; + return (this._webgl2 && this._textureFloatLinear) || (!this._webgl2 && this._textureHalfFloatLinear); } }, From 395a795f0abdb60815026997e1335d10fdd5ad98 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi <marrouchi.mohamed@gmail.com> Date: Tue, 11 Sep 2018 22:39:57 +0100 Subject: [PATCH 24/26] Move addition up to v1.50 --- CHANGES.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3f6eed7dcaf4..c120fa32a3dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ Change Log ========== +### 1.50 + +##### Additions :tada: +* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). +[#7015](https://github.com/AnalyticalGraphicsInc/cesium/pull/7015) + ### 1.49 - 2018-09-04 ##### Breaking Changes :mega: @@ -223,7 +229,6 @@ Change Log * Added ability to invoke `sampleTerrain` from node.js to enable offline terrain sampling * Added more ParticleSystem Sandcastle examples for rocket and comet tails and weather. [#6375](https://github.com/AnalyticalGraphicsInc/cesium/pull/6375) * Added color and scale attributes to the `ParticleSystem` class constructor. When defined the variables override startColor and endColor and startScale and endScale. [#6429](https://github.com/AnalyticalGraphicsInc/cesium/pull/6429) -* Added `OpenCageGeocoderService`, which provides geocoding via [OpenCage](https://opencagedata.com/). ##### Fixes :wrench: * Fixed bugs in `TimeIntervalCollection.removeInterval`. [#6418](https://github.com/AnalyticalGraphicsInc/cesium/pull/6418). From bbc738d4a506b09a925b367fb1b6f514aa14202f Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi <marrouchi.mohamed@gmail.com> Date: Tue, 11 Sep 2018 22:40:52 +0100 Subject: [PATCH 25/26] Move params into the contructor + fix the example code --- Source/Core/OpenCageGeocoderService.js | 68 ++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/Source/Core/OpenCageGeocoderService.js b/Source/Core/OpenCageGeocoderService.js index 0721bd5cfa54..c5cd0868739f 100644 --- a/Source/Core/OpenCageGeocoderService.js +++ b/Source/Core/OpenCageGeocoderService.js @@ -25,27 +25,41 @@ define([ * * @param {Resource|String} url The endpoint to the OpenCage server. * @param {String} apiKey The OpenCage API Key. + * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): + * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. + * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. + * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. + * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). + * @param {String} [options.jsonp] Wraps the returned JSON with a function name. + * @param {String} [options.language] An IETF format language code. + * @param {Number} [options.limit] The maximum number of results we should return. + * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. + * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. + * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. + * @param {Number} [options.no_record] When set to 1 the query contents are not logged. + * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. + * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * * @example - * // Configure a Viewer to use the OpenCage server hosted by https://geocode.earth/ + * // Configure a Viewer to use the OpenCage Geocoder * var viewer = new Cesium.Viewer('cesiumContainer', { - * geocoder: new Cesium.OpenCageGeocoderService(new Cesium.Resource({ - * url: 'https://api.opencagedata.com/geocode/v1/', - * queryParameters: { - * key: '<Your OpenCage API key>' - * } - * })) + * geocoder: new Cesium.OpenCageGeocoderService('https://api.opencagedata.com/geocode/v1/', '<API key>') * }); */ - function OpenCageGeocoderService(url, apiKey) { + function OpenCageGeocoderService(url, apiKey, params) { //>>includeStart('debug', pragmas.debug); Check.defined('url', url); Check.defined('apiKey', apiKey); + if (defined(params)) { + Check.typeOf.object('params', params); + } //>>includeEnd('debug'); - this._url = Resource.createIfNeeded(url); - this._url.appendForwardSlash(); - this._url.setQueryParameters({key: apiKey}); + url = Resource.createIfNeeded(url); + url.appendForwardSlash(); + url.setQueryParameters({key: apiKey}); + this._url = url; + this._params = defaultValue(params, {}); } defineProperties(OpenCageGeocoderService.prototype, { @@ -59,6 +73,17 @@ define([ get: function () { return this._url; } + }, + /** + * Optional params passed to OpenCage in order to customize geocoding + * @type {Object} + * @memberof {OpenCageGeocoderService.prototype} + * @readonly + */ + params: { + get: function () { + return this._params; + } } }); @@ -66,33 +91,16 @@ define([ * @function * * @param {String} query The query to be sent to the geocoder service - * @param {Object} [params] An object with the following properties (See https://opencagedata.com/api#forward-opt): - * @param {Number} [params.abbrv] When set to 1 we attempt to abbreviate and shorten the formatted string we return. - * @param {Number} [options.add_request] When set to 1 the various request parameters are added to the response for ease of debugging. - * @param {String} [options.bounds] Provides the geocoder with a hint to the region that the query resides in. - * @param {String} [options.countrycode] Restricts the results to the specified country or countries (as defined by the ISO 3166-1 Alpha 2 standard). - * @param {String} [options.jsonp] Wraps the returned JSON with a function name. - * @param {String} [options.language] An IETF format language code. - * @param {Number} [options.limit] The maximum number of results we should return. - * @param {Number} [options.min_confidence] An integer from 1-10. Only results with at least this confidence will be returned. - * @param {Number} [options.no_annotations] When set to 1 results will not contain annotations. - * @param {Number} [options.no_dedupe] When set to 1 results will not be deduplicated. - * @param {Number} [options.no_record] When set to 1 the query contents are not logged. - * @param {Number} [options.pretty] When set to 1 results are 'pretty' printed for easier reading. Useful for debugging. - * @param {String} [options.proximity] Provides the geocoder with a hint to bias results in favour of those closer to the specified location (For example: 41.40139,2.12870). * @returns {Promise<GeocoderService~Result[]>} */ - OpenCageGeocoderService.prototype.geocode = function(query, params) { + OpenCageGeocoderService.prototype.geocode = function(query) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string('query', query); - if (defined(params)) { - Check.typeOf.object('value', params); - } //>>includeEnd('debug'); var resource = this._url.getDerivedResource({ url: 'json', - queryParameters: Object.assign(defaultValue(params, {}), {q: query}) + queryParameters: Object.assign(this._params, {q: query}) }); return resource.fetchJson() .then(function (response) { From f46ff274b1fc16a0bc48adb18ca8ab7b4738b1df Mon Sep 17 00:00:00 2001 From: hpinkos <hannah@cesium.com> Date: Wed, 12 Sep 2018 11:04:12 -0400 Subject: [PATCH 26/26] code cleanup --- .../Cesium3DTilesInspector.js | 110 ++++++------------ .../CesiumInspector/CesiumInspector.js | 71 ++++------- Source/Widgets/InspectorShared.js | 76 ++++++++++++ .../Cesium3DTilesInspectorSpec.js | 10 -- 4 files changed, 136 insertions(+), 131 deletions(-) create mode 100644 Source/Widgets/InspectorShared.js diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js index c6bbfbb439cb..994334e511d3 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js @@ -6,6 +6,7 @@ define([ '../../Core/destroyObject', '../../ThirdParty/knockout', '../getElement', + '../InspectorShared', './Cesium3DTilesInspectorViewModel' ], function( Check, @@ -15,6 +16,7 @@ define([ destroyObject, knockout, getElement, + InspectorShared, Cesium3DTilesInspectorViewModel) { 'use strict'; @@ -36,7 +38,7 @@ define([ container = getElement(container); var element = document.createElement('div'); var performanceContainer = document.createElement('div'); - performanceContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : performance, "cesium-cesiumInspector-hide" : !performance}'); + performanceContainer.setAttribute('data-bind', 'visible: performance'); var viewModel = new Cesium3DTilesInspectorViewModel(scene, performanceContainer); this._viewModel = viewModel; @@ -57,20 +59,16 @@ define([ panel.className = 'cesium-cesiumInspector-dropDown'; element.appendChild(panel); - var tilesetPanelContents = document.createElement('div'); - tilesetPanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var displayPanelContents = document.createElement('div'); - displayPanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var updatePanelContents = document.createElement('div'); - updatePanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var loggingPanelContents = document.createElement('div'); - loggingPanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var tileDebugLabelsPanelContents = document.createElement('div'); - tileDebugLabelsPanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var stylePanelContents = document.createElement('div'); - stylePanelContents.className = 'cesium-cesiumInspector-sectionContent'; - var optimizationPanelContents = document.createElement('div'); - optimizationPanelContents.className = 'cesium-cesiumInspector-sectionContent'; + var createSection = InspectorShared.createSection; + var createCheckbox = InspectorShared.createCheckbox; + + var tilesetPanelContents = createSection(panel, 'Tileset', 'tilesetVisible', 'toggleTileset'); + var displayPanelContents = createSection(panel, 'Display', 'displayVisible', 'toggleDisplay'); + var updatePanelContents = createSection(panel, 'Update', 'updateVisible', 'toggleUpdate'); + var loggingPanelContents = createSection(panel, 'Logging', 'loggingVisible', 'toggleLogging'); + var tileDebugLabelsPanelContents = createSection(panel, 'Tile Debug Labels', 'tileDebugLabelsVisible', 'toggleTileDebugLabels'); + var stylePanelContents = createSection(panel, 'Style', 'styleVisible', 'toggleStyle'); + var optimizationPanelContents = createSection(panel, 'Optimization', 'optimizationVisible', 'toggleOptimization'); var properties = document.createElement('div'); properties.className = 'field-group'; @@ -84,48 +82,48 @@ define([ tilesetPanelContents.appendChild(properties); tilesetPanelContents.appendChild(makeButton('togglePickTileset', 'Pick Tileset', 'pickActive')); tilesetPanelContents.appendChild(makeButton('trimTilesCache', 'Trim Tiles Cache')); - tilesetPanelContents.appendChild(makeCheckbox('picking', 'Enable Picking')); + tilesetPanelContents.appendChild(createCheckbox('Enable Picking', 'picking')); - displayPanelContents.appendChild(makeCheckbox('colorize', 'Colorize')); - displayPanelContents.appendChild(makeCheckbox('wireframe', 'Wireframe')); - displayPanelContents.appendChild(makeCheckbox('showBoundingVolumes', 'Bounding Volumes')); - displayPanelContents.appendChild(makeCheckbox('showContentBoundingVolumes', 'Content Volumes')); - displayPanelContents.appendChild(makeCheckbox('showRequestVolumes', 'Request Volumes')); + displayPanelContents.appendChild(createCheckbox('Colorize', 'colorize')); + displayPanelContents.appendChild(createCheckbox('Wireframe', 'wireframe')); + displayPanelContents.appendChild(createCheckbox('Bounding Volumes', 'showBoundingVolumes')); + displayPanelContents.appendChild(createCheckbox('Content Volumes', 'showContentBoundingVolumes')); + displayPanelContents.appendChild(createCheckbox('Request Volumes', 'showRequestVolumes')); - displayPanelContents.appendChild(makeCheckbox('pointCloudShading', 'Point Cloud Shading')); + displayPanelContents.appendChild(createCheckbox('Point Cloud Shading', 'pointCloudShading')); var pointCloudShadingContainer = document.createElement('div'); - pointCloudShadingContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : pointCloudShading, "cesium-cesiumInspector-hide" : !pointCloudShading}'); + pointCloudShadingContainer.setAttribute('data-bind', 'visible: pointCloudShading'); pointCloudShadingContainer.appendChild(makeRangeInput('geometricErrorScale', 0, 2, 0.01, 'Geometric Error Scale')); pointCloudShadingContainer.appendChild(makeRangeInput('maximumAttenuation', 0, 32, 1, 'Maximum Attenuation')); pointCloudShadingContainer.appendChild(makeRangeInput('baseResolution', 0, 1, 0.01, 'Base Resolution')); - pointCloudShadingContainer.appendChild(makeCheckbox('eyeDomeLighting', 'Eye Dome Lighting (EDL)')); + pointCloudShadingContainer.appendChild(createCheckbox('Eye Dome Lighting (EDL)', 'eyeDomeLighting')); displayPanelContents.appendChild(pointCloudShadingContainer); var edlContainer = document.createElement('div'); - edlContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : eyeDomeLighting, "cesium-cesiumInspector-hide" : !eyeDomeLighting}'); + edlContainer.setAttribute('data-bind', 'visible: eyeDomeLighting'); edlContainer.appendChild(makeRangeInput('eyeDomeLightingStrength', 0, 2.0, 0.1, 'EDL Strength')); edlContainer.appendChild(makeRangeInput('eyeDomeLightingRadius', 0, 4.0, 0.1, 'EDL Radius')); pointCloudShadingContainer.appendChild(edlContainer); - updatePanelContents.appendChild(makeCheckbox('freezeFrame', 'Freeze Frame')); - updatePanelContents.appendChild(makeCheckbox('dynamicScreenSpaceError', 'Dynamic Screen Space Error')); + updatePanelContents.appendChild(createCheckbox('Freeze Frame', 'freezeFrame')); + updatePanelContents.appendChild(createCheckbox('Dynamic Screen Space Error', 'dynamicScreenSpaceError')); var sseContainer = document.createElement('div'); sseContainer.appendChild(makeRangeInput('maximumScreenSpaceError', 0, 128, 1, 'Maximum Screen Space Error')); updatePanelContents.appendChild(sseContainer); var dynamicScreenSpaceErrorContainer = document.createElement('div'); - dynamicScreenSpaceErrorContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : dynamicScreenSpaceError, "cesium-cesiumInspector-hide" : !dynamicScreenSpaceError}'); + dynamicScreenSpaceErrorContainer.setAttribute('data-bind', 'visible: dynamicScreenSpaceError'); dynamicScreenSpaceErrorContainer.appendChild(makeRangeInput('dynamicScreenSpaceErrorDensitySliderValue', 0, 1, 0.005, 'Screen Space Error Density', 'dynamicScreenSpaceErrorDensity')); dynamicScreenSpaceErrorContainer.appendChild(makeRangeInput('dynamicScreenSpaceErrorFactor', 1, 10, 0.1, 'Screen Space Error Factor')); updatePanelContents.appendChild(dynamicScreenSpaceErrorContainer); - loggingPanelContents.appendChild(makeCheckbox('performance', 'Performance')); + loggingPanelContents.appendChild(createCheckbox('Performance', 'performance')); loggingPanelContents.appendChild(performanceContainer); - loggingPanelContents.appendChild(makeCheckbox('showStatistics', 'Statistics')); + loggingPanelContents.appendChild(createCheckbox('Statistics', 'showStatistics')); var statistics = document.createElement('div'); statistics.className = 'cesium-3dTilesInspector-statistics'; statistics.setAttribute('data-bind', 'html: statisticsText, visible: showStatistics'); loggingPanelContents.appendChild(statistics); - loggingPanelContents.appendChild(makeCheckbox('showPickStatistics', 'Pick Statistics')); + loggingPanelContents.appendChild(createCheckbox('Pick Statistics', 'showPickStatistics')); var pickStatistics = document.createElement('div'); pickStatistics.className = 'cesium-3dTilesInspector-statistics'; pickStatistics.setAttribute('data-bind', 'html: pickStatisticsText, visible: showPickStatistics'); @@ -151,13 +149,13 @@ define([ errorBox.setAttribute('data-bind', 'text: editorError'); stylePanelEditor.appendChild(errorBox); - tileDebugLabelsPanelContents.appendChild(makeCheckbox('showOnlyPickedTileDebugLabel', 'Show Picked Only')); - tileDebugLabelsPanelContents.appendChild(makeCheckbox('showGeometricError', 'Geometric Error')); - tileDebugLabelsPanelContents.appendChild(makeCheckbox('showRenderingStatistics', 'Rendering Statistics')); - tileDebugLabelsPanelContents.appendChild(makeCheckbox('showMemoryUsage', 'Memory Usage (MB)')); - tileDebugLabelsPanelContents.appendChild(makeCheckbox('showUrl', 'Url')); + tileDebugLabelsPanelContents.appendChild(createCheckbox('Show Picked Only', 'showOnlyPickedTileDebugLabel')); + tileDebugLabelsPanelContents.appendChild(createCheckbox('Geometric Error', 'showGeometricError')); + tileDebugLabelsPanelContents.appendChild(createCheckbox('Rendering Statistics', 'showRenderingStatistics')); + tileDebugLabelsPanelContents.appendChild(createCheckbox('Memory Usage (MB)', 'showMemoryUsage')); + tileDebugLabelsPanelContents.appendChild(createCheckbox('Url', 'showUrl')); - optimizationPanelContents.appendChild(makeCheckbox('skipLevelOfDetail', 'Skip Tile LODs')); + optimizationPanelContents.appendChild(createCheckbox('Skip Tile LODs', 'skipLevelOfDetail')); var skipScreenSpaceErrorFactorContainer = document.createElement('div'); skipScreenSpaceErrorFactorContainer.appendChild(makeRangeInput('skipScreenSpaceErrorFactor', 1, 50, 1, 'Skip SSE Factor')); optimizationPanelContents.appendChild(skipScreenSpaceErrorFactorContainer); @@ -167,15 +165,8 @@ define([ var skipLevelsContainer = document.createElement('div'); skipLevelsContainer.appendChild(makeRangeInput('skipLevels', 0, 10, 1, 'Min. levels to skip')); optimizationPanelContents.appendChild(skipLevelsContainer); - optimizationPanelContents.appendChild(makeCheckbox('immediatelyLoadDesiredLevelOfDetail', 'Load only tiles that meet the max. SSE.')); - optimizationPanelContents.appendChild(makeCheckbox('loadSiblings', 'Load siblings of visible tiles.')); - - makeSection(panel, 'Tileset', 'tilesetVisible', 'toggleTileset', tilesetPanelContents); - makeSection(panel, 'Display', 'displayVisible', 'toggleDisplay', displayPanelContents); - makeSection(panel, 'Logging', 'loggingVisible', 'toggleLogging', loggingPanelContents); - makeSection(panel, 'Tile Debug Labels', 'tileDebugLabelsVisible', 'toggleTileDebugLabels', tileDebugLabelsPanelContents); - makeSection(panel, 'Style', 'styleVisible', 'toggleStyle', stylePanelContents); - makeSection(panel, 'Optimization', 'optimizationVisible', 'toggleOptimization', optimizationPanelContents); + optimizationPanelContents.appendChild(createCheckbox('Load only tiles that meet the max SSE.', 'immediatelyLoadDesiredLevelOfDetail')); + optimizationPanelContents.appendChild(createCheckbox('Load siblings of visible tiles', 'loadSiblings')); knockout.applyBindings(viewModel, element); } @@ -225,33 +216,6 @@ define([ return destroyObject(this); }; - function makeSection(panel, name, visibleProp, toggleProp, contents) { - var header = document.createElement('div'); - header.className = 'cesium-cesiumInspector-sectionHeader'; - header.setAttribute('data-bind', 'click: ' + toggleProp); - header.appendChild(document.createTextNode(name)); - - var section = document.createElement('div'); - section.className = 'cesium-cesiumInspector-section'; - section.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-section-collapsed" : !' + visibleProp + '}'); - section.appendChild(header); - section.appendChild(contents); - - panel.appendChild(section); - } - - function makeCheckbox(property, text) { - var checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.setAttribute('data-bind', 'checked: ' + property); - - var container = document.createElement('div'); - container.appendChild(checkbox); - container.appendChild(document.createTextNode(text)); - - return container; - } - function makeRangeInput(property, min, max, step, text, displayProperty) { displayProperty = defaultValue(displayProperty, property); var input = document.createElement('input'); diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js index f03f2ad0c6f9..c17827feb576 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.js +++ b/Source/Widgets/CesiumInspector/CesiumInspector.js @@ -5,6 +5,7 @@ define([ '../../Core/DeveloperError', '../../ThirdParty/knockout', '../getElement', + '../InspectorShared', './CesiumInspectorViewModel' ], function( defined, @@ -13,39 +14,10 @@ define([ DeveloperError, knockout, getElement, + InspectorShared, CesiumInspectorViewModel) { 'use strict'; - function createCheckBox(checkboxBinding, labelText) { - var checkboxContainer = document.createElement('div'); - var checkboxLabel = document.createElement('label'); - var checkboxInput = document.createElement('input'); - checkboxInput.type = 'checkbox'; - checkboxInput.setAttribute('data-bind', checkboxBinding); - checkboxLabel.appendChild(checkboxInput); - checkboxLabel.appendChild(document.createTextNode(labelText)); - checkboxContainer.appendChild(checkboxLabel); - return checkboxContainer; - } - - function addSection(panel, headerText, sectionVisibleDataBinding, sectionVisibilityToggleClickEvent) { - var section = document.createElement('div'); - section.className = 'cesium-cesiumInspector-section'; - section.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-section-collapsed": !' + sectionVisibleDataBinding + ' }'); - panel.appendChild(section); - - var sectionHeader = document.createElement('h3'); - sectionHeader.className = 'cesium-cesiumInspector-sectionHeader'; - sectionHeader.appendChild(document.createTextNode(headerText)); - sectionHeader.setAttribute('data-bind', 'click: ' + sectionVisibilityToggleClickEvent); - section.appendChild(sectionHeader); - - var sectionContent = document.createElement('div'); - sectionContent.className = 'cesium-cesiumInspector-sectionContent'; - section.appendChild(sectionContent); - return sectionContent; - } - /** * Inspector widget to aid in debugging * @@ -92,18 +64,21 @@ define([ panel.className = 'cesium-cesiumInspector-dropDown'; element.appendChild(panel); + var createSection = InspectorShared.createSection; + var createCheckbox = InspectorShared.createCheckbox; + // General - var generalSection = addSection(panel, 'General', 'generalVisible', 'toggleGeneral'); + var generalSection = createSection(panel, 'General', 'generalVisible', 'toggleGeneral'); - var debugShowFrustums = createCheckBox('checked: frustums', 'Show Frustums'); + var debugShowFrustums = createCheckbox('Show Frustums', 'frustums'); var frustumStatistics = document.createElement('div'); frustumStatistics.className = 'cesium-cesiumInspector-frustumStatistics'; - frustumStatistics.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : frustums, "cesium-cesiumInspector-hide" : !frustums}, html: frustumStatisticText'); + frustumStatistics.setAttribute('data-bind', 'visible: frustums, html: frustumStatisticText'); debugShowFrustums.appendChild(frustumStatistics); generalSection.appendChild(debugShowFrustums); - generalSection.appendChild(createCheckBox('checked: frustumPlanes', 'Show Frustum Planes')); - generalSection.appendChild(createCheckBox('checked: performance', 'Performance Display')); + generalSection.appendChild(createCheckbox('Show Frustum Planes', 'frustumPlanes')); + generalSection.appendChild(createCheckbox('Performance Display', 'performance')); performanceContainer.className = 'cesium-cesiumInspector-performanceDisplay'; generalSection.appendChild(performanceContainer); @@ -114,13 +89,13 @@ define([ generalSection.appendChild(shaderCacheDisplay); // https://github.com/AnalyticalGraphicsInc/cesium/issues/6763 - // var globeDepth = createCheckBox('checked: globeDepth', 'Show globe depth'); + // var globeDepth = createCheckbox('Show globe depth', 'globeDepth'); // generalSection.appendChild(globeDepth); // // var globeDepthFrustum = document.createElement('div'); // globeDepth.appendChild(globeDepthFrustum); // - // generalSection.appendChild(createCheckBox('checked: pickDepth', 'Show pick depth')); + // generalSection.appendChild(createCheckbox('Show pick depth', 'pickDepth')); var depthFrustum = document.createElement('div'); generalSection.appendChild(depthFrustum); @@ -149,7 +124,7 @@ define([ depthFrustum.appendChild(gPlusButton); // Primitives - var primSection = addSection(panel, 'Primitives', 'primitivesVisible', 'togglePrimitives'); + var primSection = createSection(panel, 'Primitives', 'primitivesVisible', 'togglePrimitives'); var pickPrimRequired = document.createElement('div'); pickPrimRequired.className = 'cesium-cesiumInspector-pickSection'; primSection.appendChild(pickPrimRequired); @@ -164,14 +139,14 @@ define([ buttonWrap.appendChild(pickPrimitiveButton); pickPrimRequired.appendChild(buttonWrap); - pickPrimRequired.appendChild(createCheckBox('checked: primitiveBoundingSphere, enable: hasPickedPrimitive', 'Show bounding sphere')); - pickPrimRequired.appendChild(createCheckBox('checked: primitiveReferenceFrame, enable: hasPickedPrimitive', 'Show reference frame')); + pickPrimRequired.appendChild(createCheckbox('Show bounding sphere', 'primitiveBoundingSphere', 'hasPickedPrimitive')); + pickPrimRequired.appendChild(createCheckbox('Show reference frame', 'primitiveReferenceFrame', 'hasPickedPrimitive')); - this._primitiveOnly = createCheckBox('checked: filterPrimitive, enable: hasPickedPrimitive', 'Show only selected'); + this._primitiveOnly = createCheckbox('Show only selected', 'filterPrimitive', 'hasPickedPrimitive'); pickPrimRequired.appendChild(this._primitiveOnly); // Terrain - var terrainSection = addSection(panel, 'Terrain', 'terrainVisible', 'toggleTerrain'); + var terrainSection = createSection(panel, 'Terrain', 'terrainVisible', 'toggleTerrain'); var pickTileRequired = document.createElement('div'); pickTileRequired.className = 'cesium-cesiumInspector-pickSection'; terrainSection.appendChild(pickTileRequired); @@ -216,7 +191,7 @@ define([ tileText.className = 'cesium-cesiumInspector-tileText'; tileInfo.className = 'cesium-cesiumInspector-frustumStatistics'; tileInfo.appendChild(tileText); - tileInfo.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : hasPickedTile, "cesium-cesiumInspector-hide" : !hasPickedTile}'); + tileInfo.setAttribute('data-bind', 'visible: hasPickedTile'); tileText.setAttribute('data-bind', 'html: tileText'); var relativeText = document.createElement('div'); @@ -249,12 +224,12 @@ define([ tileInfo.appendChild(table); - pickTileRequired.appendChild(createCheckBox('checked: tileBoundingSphere, enable: hasPickedTile', 'Show bounding volume')); - pickTileRequired.appendChild(createCheckBox('checked: filterTile, enable: hasPickedTile', 'Show only selected')); + pickTileRequired.appendChild(createCheckbox('Show bounding volume', 'tileBoundingSphere', 'hasPickedTile')); + pickTileRequired.appendChild(createCheckbox('Show only selected', 'filterTile', 'hasPickedTile')); - terrainSection.appendChild(createCheckBox('checked: wireframe', 'Wireframe')); - terrainSection.appendChild(createCheckBox('checked: suspendUpdates', 'Suspend LOD update')); - terrainSection.appendChild(createCheckBox('checked: tileCoordinates', 'Show tile coordinates')); + terrainSection.appendChild(createCheckbox('Wireframe', 'wireframe')); + terrainSection.appendChild(createCheckbox('Suspend LOD update', 'suspendUpdates')); + terrainSection.appendChild(createCheckbox('Show tile coordinates', 'tileCoordinates')); knockout.applyBindings(viewModel, this._element); } diff --git a/Source/Widgets/InspectorShared.js b/Source/Widgets/InspectorShared.js new file mode 100644 index 000000000000..db410773c57f --- /dev/null +++ b/Source/Widgets/InspectorShared.js @@ -0,0 +1,76 @@ +define([ + '../Core/defined', + '../Core/Check' + ],function( + defined, + Check) { + 'use strict'; + + /** + * A static class with helper functions used by the CesiumInspector and Cesium3DTilesInspector + * @private + */ + var InspectorShared = {}; + + /** + * Creates a checkbox component + * @param {String} labelText The text to display in the checkbox label + * @param {String} checkedBinding The name of the variable used for checked binding + * @param {String} [enableBinding] The name of the variable used for enable binding + * @return {Element} + */ + InspectorShared.createCheckbox = function (labelText, checkedBinding, enableBinding) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('labelText', labelText); + Check.typeOf.string('checkedBinding', checkedBinding); + //>>includeEnd('debug'); + var checkboxContainer = document.createElement('div'); + var checkboxLabel = document.createElement('label'); + var checkboxInput = document.createElement('input'); + checkboxInput.type = 'checkbox'; + + var binding = 'checked: ' + checkedBinding; + if (defined(enableBinding)) { + binding += ', enable: ' + enableBinding; + } + checkboxInput.setAttribute('data-bind', binding); + checkboxLabel.appendChild(checkboxInput); + checkboxLabel.appendChild(document.createTextNode(labelText)); + checkboxContainer.appendChild(checkboxLabel); + return checkboxContainer; + }; + + /** + * Creates a section element + * @param {Element} panel The parent element + * @param {String} headerText The text to display at the top of the section + * @param {String} sectionVisibleBinding The name of the variable used for visible binding + * @param {String} toggleSectionVisibilityBinding The name of the function used to toggle visibility + * @return {Element} + */ + InspectorShared.createSection = function (panel, headerText, sectionVisibleBinding, toggleSectionVisibilityBinding) { + //>>includeStart('debug', pragmas.debug); + Check.defined('panel', panel); + Check.typeOf.string('headerText', headerText); + Check.typeOf.string('sectionVisibleBinding', sectionVisibleBinding); + Check.typeOf.string('toggleSectionVisibilityBinding', toggleSectionVisibilityBinding); + //>>includeEnd('debug'); + var section = document.createElement('div'); + section.className = 'cesium-cesiumInspector-section'; + section.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-section-collapsed": !' + sectionVisibleBinding + ' }'); + panel.appendChild(section); + + var sectionHeader = document.createElement('h3'); + sectionHeader.className = 'cesium-cesiumInspector-sectionHeader'; + sectionHeader.appendChild(document.createTextNode(headerText)); + sectionHeader.setAttribute('data-bind', 'click: ' + toggleSectionVisibilityBinding); + section.appendChild(sectionHeader); + + var sectionContent = document.createElement('div'); + sectionContent.className = 'cesium-cesiumInspector-sectionContent'; + section.appendChild(sectionContent); + return sectionContent; + }; + + return InspectorShared; +}); diff --git a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorSpec.js b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorSpec.js index 22bf9f44db6a..cea3f6a047ed 100644 --- a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorSpec.js +++ b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorSpec.js @@ -80,15 +80,5 @@ defineSuite([ widget.destroy(); document.body.removeChild(container); }); - - it('shows performance', function() { - var viewModel = widget.viewModel; - viewModel.performance = true; - expect(viewModel._performanceDisplay._container.className.indexOf('cesium-cesiumInspector-show') !== -1).toBe(true); - expect(viewModel._performanceDisplay._container.className.indexOf('cesium-cesiumInspector-hide') === -1).toBe(true); - viewModel.performance = false; - expect(viewModel._performanceDisplay._container.className.indexOf('cesium-cesiumInspector-show') === -1).toBe(true); - expect(viewModel._performanceDisplay._container.className.indexOf('cesium-cesiumInspector-hide') !== -1).toBe(true); - }); }); }, 'WebGL');