A tile queue that can be aborted.
This saves server requests, and because we use OpenLayers.Animation, setting img.src on a tile should not freeze iOS any more, so we can hopefully get rid of scheduleMoveGriddedTiles.
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
|
||||
<link rel="stylesheet" href="mobile-wmts-vienna.css" type="text/css">
|
||||
<script src="../lib/OpenLayers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="title">City of Vienna WMTS for Desktop and Mobile Devices</h1>
|
||||
@@ -22,6 +22,7 @@
|
||||
functionality and uses the Geolocate control.
|
||||
</p>
|
||||
<div id="map"></div>
|
||||
<script src="../lib/OpenLayers.js"></script>
|
||||
<script src="mobile-wmts-vienna.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -152,7 +152,6 @@ var map;
|
||||
var defaults = {
|
||||
requestEncoding: "REST",
|
||||
matrixSet: "google3857",
|
||||
buffer: 4,
|
||||
attribution: 'Datenquelle: Stadt Wien - <a href="http://data.wien.gv.at">data.wien.gv.at</a>'
|
||||
};
|
||||
var doc = request.responseText,
|
||||
@@ -182,7 +181,6 @@ var map;
|
||||
requestEncoding: "REST",
|
||||
matrixSet: "google3857",
|
||||
tileFullExtent: extent,
|
||||
buffer: 4,
|
||||
attribution: 'Datenquelle: Stadt Wien - <a href="http://data.wien.gv.at">data.wien.gv.at</a>'
|
||||
};
|
||||
fmzk = new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults({
|
||||
|
||||
@@ -97,13 +97,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
*/
|
||||
numLoadingTiles: 0,
|
||||
|
||||
/**
|
||||
* APIProperty: tileLoadingDelay
|
||||
* {Integer} - Number of milliseconds before we shift and load
|
||||
* tiles. Default is 100.
|
||||
*/
|
||||
tileLoadingDelay: 100,
|
||||
|
||||
/**
|
||||
* Property: serverResolutions
|
||||
* {Array(Number}} This property is documented in subclasses as
|
||||
@@ -112,10 +105,16 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
serverResolutions: null,
|
||||
|
||||
/**
|
||||
* Property: timerId
|
||||
* {Number} - The id of the tileLoadingDelay timer.
|
||||
* Property: tileLoopId
|
||||
* {Number} - The id of the <doTileOperation> animation.
|
||||
*/
|
||||
timerId: null,
|
||||
tileLoopId: null,
|
||||
|
||||
/**
|
||||
* Property: tileOperations
|
||||
* {Array(Object)} Pending tile operations.
|
||||
*/
|
||||
tileOperations: null,
|
||||
|
||||
/**
|
||||
* Property: backBuffer
|
||||
@@ -187,10 +186,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
|
||||
arguments);
|
||||
this.grid = [];
|
||||
|
||||
this._moveGriddedTiles = OpenLayers.Function.bind(
|
||||
this.moveGriddedTiles, this
|
||||
);
|
||||
this.tileOperations = [];
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -201,10 +197,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
* map - {<OpenLayers.Map>} The map.
|
||||
*/
|
||||
removeMap: function(map) {
|
||||
if(this.timerId != null) {
|
||||
window.clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
this.abortTileOperations();
|
||||
if(this.backBufferTimerId !== null) {
|
||||
window.clearTimeout(this.backBufferTimerId);
|
||||
this.backBufferTimerId = null;
|
||||
@@ -230,13 +223,13 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
* destroy() on each of them to kill circular references
|
||||
*/
|
||||
clearGrid:function() {
|
||||
this.abortTileOperations();
|
||||
if (this.grid) {
|
||||
for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
|
||||
var row = this.grid[iRow];
|
||||
for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
|
||||
var tile = row[iCol];
|
||||
this.removeTileMonitoringHooks(tile);
|
||||
tile.destroy();
|
||||
this.destroyTile(tile);
|
||||
}
|
||||
}
|
||||
this.grid = [];
|
||||
@@ -300,6 +293,10 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
// if grid is empty or zoom has changed, we *must* re-tile
|
||||
var forceReTile = !this.grid.length || zoomChanged;
|
||||
|
||||
if (forceReTile) {
|
||||
this.abortTileOperations();
|
||||
}
|
||||
|
||||
// total bounds of the tiles
|
||||
var tilesBounds = this.getTilesBounds();
|
||||
|
||||
@@ -371,12 +368,69 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
}
|
||||
this.initGriddedTiles(bounds);
|
||||
} else {
|
||||
this.scheduleMoveGriddedTiles();
|
||||
this.moveGriddedTiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: addTileOperation
|
||||
* Adds a tile operation to the animation queue.
|
||||
*
|
||||
* Parameters:
|
||||
* fn - {Function} The function to execute
|
||||
* scope - {Object} The execution scope for the function
|
||||
*/
|
||||
addTileOperation: function(fn, scope) {
|
||||
this.tileOperations.push({fn: fn, scope: scope});
|
||||
if (!this.tileLoopId) {
|
||||
this.tileLoopId = OpenLayers.Animation.start(
|
||||
OpenLayers.Function.bind(this.doTileOperation, this),
|
||||
null, this.div
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: doTileOperation
|
||||
* Executes the first tile operation from the animation queue.
|
||||
*/
|
||||
doTileOperation: function() {
|
||||
if (this.tileOperations.length === 0) {
|
||||
this.abortTileOperations();
|
||||
} else {
|
||||
var operation = this.tileOperations.shift();
|
||||
operation.fn.call(operation.scope);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: abortTileOperations
|
||||
* Stops the animation queue and removes all pending operations
|
||||
*/
|
||||
abortTileOperations: function() {
|
||||
OpenLayers.Animation.stop(this.tileLoopId);
|
||||
this.tileLoopId = null;
|
||||
this.tileOperations = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: destroyTile
|
||||
*
|
||||
* Parameters:
|
||||
* tile - {<OpenLayers.Tile>}
|
||||
*/
|
||||
destroyTile: function(tile) {
|
||||
for (var i=this.tileOperations.length-1; i>=0; --i) {
|
||||
if (this.tileOperations[i].scope === tile) {
|
||||
this.tileOperations.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.removeTileMonitoringHooks(tile);
|
||||
tile.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: getServerResolution
|
||||
* Return the closest highest server-supported resolution. Throw an
|
||||
@@ -565,24 +619,10 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
*/
|
||||
moveByPx: function(dx, dy) {
|
||||
if (!this.singleTile) {
|
||||
this.scheduleMoveGriddedTiles();
|
||||
this.moveGriddedTiles();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: scheduleMoveGriddedTiles
|
||||
* Schedule the move of tiles.
|
||||
*/
|
||||
scheduleMoveGriddedTiles: function() {
|
||||
if (this.timerId != null) {
|
||||
window.clearTimeout(this.timerId);
|
||||
}
|
||||
this.timerId = window.setTimeout(
|
||||
this._moveGriddedTiles,
|
||||
this.tileLoadingDelay
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* APIMethod: setTileSize
|
||||
* Check if we are in singleTile mode and if so, set the size as a ratio
|
||||
@@ -776,7 +816,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left);
|
||||
var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
|
||||
|
||||
|
||||
var tileData = [], center = this.map.getCenter();
|
||||
do {
|
||||
var row = this.grid[rowidx++];
|
||||
if (!row) {
|
||||
@@ -810,6 +850,12 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
} else {
|
||||
tile.moveTo(tileBounds, px, false);
|
||||
}
|
||||
var tileCenter = tileBounds.getCenterLonLat();
|
||||
tileData.push({
|
||||
tile: tile,
|
||||
distance: Math.pow(tileCenter.lon - center.lon, 2) +
|
||||
Math.pow(tileCenter.lat - center.lat, 2)
|
||||
});
|
||||
|
||||
tileoffsetlon += tilelon;
|
||||
tileoffsetx += this.tileSize.w;
|
||||
@@ -828,7 +874,12 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
this.gridResolution = this.getServerResolution();
|
||||
|
||||
//now actually draw the tiles
|
||||
this.spiralTileLoad();
|
||||
tileData.sort(function(a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
for (var i=0, ii=tileData.length; i<ii; ++i) {
|
||||
tileData[i].tile.draw();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -843,79 +894,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
return this.maxExtent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: spiralTileLoad
|
||||
* Starts at the top right corner of the grid and proceeds in a spiral
|
||||
* towards the center, adding tiles one at a time to the beginning of a
|
||||
* queue.
|
||||
*
|
||||
* Once all the grid's tiles have been added to the queue, we go back
|
||||
* and iterate through the queue (thus reversing the spiral order from
|
||||
* outside-in to inside-out), calling draw() on each tile.
|
||||
*/
|
||||
spiralTileLoad: function() {
|
||||
var tileQueue = [];
|
||||
|
||||
var directions = ["right", "down", "left", "up"];
|
||||
|
||||
var iRow = 0;
|
||||
var iCell = -1;
|
||||
var direction = OpenLayers.Util.indexOf(directions, "right");
|
||||
var directionsTried = 0;
|
||||
|
||||
while( directionsTried < directions.length) {
|
||||
|
||||
var testRow = iRow;
|
||||
var testCell = iCell;
|
||||
|
||||
switch (directions[direction]) {
|
||||
case "right":
|
||||
testCell++;
|
||||
break;
|
||||
case "down":
|
||||
testRow++;
|
||||
break;
|
||||
case "left":
|
||||
testCell--;
|
||||
break;
|
||||
case "up":
|
||||
testRow--;
|
||||
break;
|
||||
}
|
||||
|
||||
// if the test grid coordinates are within the bounds of the
|
||||
// grid, get a reference to the tile.
|
||||
var tile = null;
|
||||
if ((testRow < this.grid.length) && (testRow >= 0) &&
|
||||
(testCell < this.grid[0].length) && (testCell >= 0)) {
|
||||
tile = this.grid[testRow][testCell];
|
||||
}
|
||||
|
||||
if ((tile != null) && (!tile.queued)) {
|
||||
//add tile to beginning of queue, mark it as queued.
|
||||
tileQueue.unshift(tile);
|
||||
tile.queued = true;
|
||||
|
||||
//restart the directions counter and take on the new coords
|
||||
directionsTried = 0;
|
||||
iRow = testRow;
|
||||
iCell = testCell;
|
||||
} else {
|
||||
//need to try to load a tile in a different direction
|
||||
direction = (direction + 1) % 4;
|
||||
directionsTried++;
|
||||
}
|
||||
}
|
||||
|
||||
// now we go through and draw the tiles in forward order
|
||||
for(var i=0, len=tileQueue.length; i<len; i++) {
|
||||
var tile = tileQueue[i];
|
||||
tile.draw();
|
||||
//mark tile as unqueued for the next time (since tiles are reused)
|
||||
tile.queued = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* APIMethod: addTile
|
||||
* Create a tile, initialize it, and add it to the layer div.
|
||||
@@ -928,8 +906,17 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
* {<OpenLayers.Tile>} The added OpenLayers.Tile
|
||||
*/
|
||||
addTile: function(bounds, position) {
|
||||
var that = this;
|
||||
var options = OpenLayers.Util.extend({
|
||||
draw: function() {
|
||||
// clear immediately
|
||||
this.clear();
|
||||
// draw in an animation frame - can be aborted
|
||||
that.addTileOperation(that.tileClass.prototype.draw, this);
|
||||
}
|
||||
}, this.tileOptions);
|
||||
return new this.tileClass(this, position, bounds, null,
|
||||
this.tileSize, this.tileOptions);
|
||||
this.tileSize, options);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -993,37 +980,32 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
* Method: moveGriddedTiles
|
||||
*/
|
||||
moveGriddedTiles: function() {
|
||||
var shifted = true;
|
||||
var buffer = this.buffer || 1;
|
||||
var scale = this.getResolutionScale();
|
||||
var tlLayer = this.grid[0][0].position.clone();
|
||||
tlLayer.x *= scale;
|
||||
tlLayer.y *= scale;
|
||||
tlLayer = tlLayer.add(parseInt(this.div.style.left, 10),
|
||||
parseInt(this.div.style.top, 10));
|
||||
var offsetX = parseInt(this.map.layerContainerDiv.style.left);
|
||||
var offsetY = parseInt(this.map.layerContainerDiv.style.top);
|
||||
var tlViewPort = tlLayer.add(offsetX, offsetY);
|
||||
var tileSize = {
|
||||
w: this.tileSize.w * scale,
|
||||
h: this.tileSize.h * scale
|
||||
};
|
||||
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
|
||||
this.shiftColumn(true);
|
||||
} else if (tlViewPort.x < -tileSize.w * buffer) {
|
||||
this.shiftColumn(false);
|
||||
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
|
||||
this.shiftRow(true);
|
||||
} else if (tlViewPort.y < -tileSize.h * buffer) {
|
||||
this.shiftRow(false);
|
||||
} else {
|
||||
shifted = false;
|
||||
}
|
||||
if (shifted) {
|
||||
// we may have other row or columns to shift, schedule it
|
||||
// with a setTimeout, to give the user a chance to sneak
|
||||
// in moveTo's
|
||||
this.timerId = window.setTimeout(this._moveGriddedTiles, 0);
|
||||
while(true) {
|
||||
var tlLayer = this.grid[0][0].position.clone();
|
||||
tlLayer.x *= scale;
|
||||
tlLayer.y *= scale;
|
||||
tlLayer = tlLayer.add(parseInt(this.div.style.left, 10),
|
||||
parseInt(this.div.style.top, 10));
|
||||
var offsetX = parseInt(this.map.layerContainerDiv.style.left);
|
||||
var offsetY = parseInt(this.map.layerContainerDiv.style.top);
|
||||
var tlViewPort = tlLayer.add(offsetX, offsetY);
|
||||
var tileSize = {
|
||||
w: this.tileSize.w * scale,
|
||||
h: this.tileSize.h * scale
|
||||
};
|
||||
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
|
||||
this.shiftColumn(true);
|
||||
} else if (tlViewPort.x < -tileSize.w * buffer) {
|
||||
this.shiftColumn(false);
|
||||
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
|
||||
this.shiftRow(true);
|
||||
} else if (tlViewPort.y < -tileSize.h * buffer) {
|
||||
this.shiftRow(false);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1113,8 +1095,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
var row = this.grid.pop();
|
||||
for (var i=0, l=row.length; i<l; i++) {
|
||||
var tile = row[i];
|
||||
this.removeTileMonitoringHooks(tile);
|
||||
tile.destroy();
|
||||
this.destroyTile(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,8 +1104,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
|
||||
for (var i=0, l=this.grid.length; i<l; i++) {
|
||||
var row = this.grid[i];
|
||||
var tile = row.pop();
|
||||
this.removeTileMonitoringHooks(tile);
|
||||
tile.destroy();
|
||||
this.destroyTile(tile);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<script type="text/javascript">window.alert = oldAlert;</script>
|
||||
<script src="../OLLoader.js"></script>
|
||||
<script type="text/javascript">
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
<head>
|
||||
<script src="../OLLoader.js"></script>
|
||||
<script type="text/javascript">
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
var origAddTileOperation = OpenLayers.Layer.Grid.prototype.addTileOperation;
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
@@ -189,7 +195,7 @@
|
||||
|
||||
function test_Layer_Grid_moveTo(t) {
|
||||
|
||||
t.plan(14);
|
||||
t.plan(13);
|
||||
|
||||
var map = new OpenLayers.Map('map');
|
||||
layer = new OpenLayers.Layer.WMS(name, url, params);
|
||||
@@ -214,7 +220,7 @@
|
||||
g_WhichFunc = "InitGridded";
|
||||
g_Bounds = bounds;
|
||||
};
|
||||
layer._moveGriddedTiles = function() {
|
||||
layer.moveGriddedTiles = function() {
|
||||
g_WhichFunc = "MoveGridded";
|
||||
g_Bounds = layer.map.getExtent();
|
||||
};
|
||||
@@ -307,7 +313,7 @@
|
||||
|
||||
|
||||
//gridded
|
||||
layer.grid = [ [ {} ] ];
|
||||
layer.grid = [ [ {position: new OpenLayers.Pixel(0,0)} ] ];
|
||||
layer.singleTile = false;
|
||||
|
||||
//regular move
|
||||
@@ -315,11 +321,8 @@
|
||||
tilesBounds = new OpenLayers.Bounds(10,10,120,120);
|
||||
g_WhichFunc = null;
|
||||
layer.moveTo(null, zoomChanged);
|
||||
t.eq(g_WhichFunc, null, "moveGriddedTiles is delayed - not called yet");
|
||||
t.delay_call(0.2, function() {
|
||||
t.ok(g_WhichFunc == "MoveGridded", "if tiles not drastically out of bounds, we call moveGriddedTile()");
|
||||
t.ok(g_Bounds.equals(b), "if tiles not drastically out of bounds, we call moveGriddedTile() with correct bounds");
|
||||
});
|
||||
t.eq(g_WhichFunc, "MoveGridded", "if tiles not drastically out of bounds, we call moveGriddedTile()");
|
||||
t.ok(g_Bounds.equals(b), "if tiles not drastically out of bounds, we call moveGriddedTile() with correct bounds");
|
||||
|
||||
// drastic pan
|
||||
clearTestBounds();
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
<script src="../OLLoader.js"></script>
|
||||
<script type="text/javascript">
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
<script type="text/javascript">window.alert = oldAlert;</script>
|
||||
<script src="../OLLoader.js"></script>
|
||||
<script type="text/javascript">
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
<head>
|
||||
<script src="../OLLoader.js"></script>
|
||||
<script type="text/javascript">
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
<script src="../../../../lib/deprecated.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// turn off animation frame handling, so we can check img urls in tests
|
||||
OpenLayers.Layer.Grid.prototype.addTileOperation = function(fn, scope) {
|
||||
fn.call(scope);
|
||||
};
|
||||
|
||||
var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
|
||||
var layer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user