This example shows how to use the CacheRead control to fetch cached
+ tiles from the browser's Local Storage. As you pan and zoom the map,
+ you can see how the number of cache hits incrases as you browse regions
+ that are available in the cache.
+
To fill the cache with tiles, switch to the
+ cache-write.html example.
+
+
diff --git a/examples/cache-read.js b/examples/cache-read.js
new file mode 100644
index 0000000000..1f7988984e
--- /dev/null
+++ b/examples/cache-read.js
@@ -0,0 +1,36 @@
+var map, cacheRead;
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ }, {
+ eventListeners: {
+ tileloaded: updateHits
+ }
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ cacheRead = new OpenLayers.Control.CacheRead();
+ map.addControl(cacheRead);
+
+
+
+ // User interface
+ var status = document.getElementById("status"),
+ hits = 0;
+
+ // update the number of cached tiles and detect local storage support
+ function updateHits(evt) {
+ hits += evt.tile.url.substr(0, 5) === "data:";
+ if (window.localStorage) {
+ status.innerHTML = hits + " cache hits.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/cache-write.html b/examples/cache-write.html
new file mode 100644
index 0000000000..ca59f68d80
--- /dev/null
+++ b/examples/cache-write.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ OpenLayers Cache Write Example
+
+
+
+
+
+
+
+
Cache Write Example
+
+
+ local storage, persistence, cache, html5
+
+
+
Caching viewed tiles
+
+
+
Cache status:
+
+
+
+
This example shows how to use the CacheWrite control to cache the
+ tiles. Caching is turned on, and as you pan and zoom the map, every
+ tile that is loaded is also copied to the browsers Local Storage.
+
To use the cached tiles, switch to the
+ cache-read.html example.
+
+
diff --git a/examples/cache-write.js b/examples/cache-write.js
new file mode 100644
index 0000000000..8f4ec9e081
--- /dev/null
+++ b/examples/cache-write.js
@@ -0,0 +1,48 @@
+// Use proxy to get same origin URLs for tiles that don't support CORS.
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var map, cacheWrite;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ }, {
+ eventListeners: {
+ tileloaded: updateStatus
+ }
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ cacheWrite = new OpenLayers.Control.CacheWrite({
+ autoActivate: true,
+ imageFormat: "image/jpeg",
+ eventListeners: {
+ cachefull: function() { status.innerHTML = "Cache full."; }
+ }
+ });
+ map.addControl(cacheWrite);
+
+
+
+ // User interface
+ var status = document.getElementById("status");
+ document.getElementById("clear").onclick = function() {
+ OpenLayers.Control.CacheWrite.clearCache();
+ updateStatus();
+ };
+
+ // update the number of cached tiles and detect local storage support
+ function updateStatus() {
+ if (window.localStorage) {
+ status.innerHTML = localStorage.length + " entries in cache.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/offline-storage.html b/examples/offline-storage.html
new file mode 100644
index 0000000000..1c510ca537
--- /dev/null
+++ b/examples/offline-storage.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ OpenLayers Offline Storage Example
+
+
+
+
+
+
+
+
+
Offline Storage Example
+
+
+ local storage, persistence, cache, html5
+
+
+
Caching viewed tiles
+
+
+
Cache status:
+
Read from cache [try cache first] [try online first1]
+
Write to cache
+
+
+
1 Disconnect your device from the network to test - only works for same origin layers.
+
+
+
This example shows how to use the CacheWrite control to cache tiles
+ that are being viewed in the browser's local storage, and how to use
+ the CacheRead control to use cached tiles when offline or on a slow
+ connection. See offline-storage.js
+ for the source code.
+
+
+
diff --git a/examples/offline-storage.js b/examples/offline-storage.js
new file mode 100644
index 0000000000..e0b5929791
--- /dev/null
+++ b/examples/offline-storage.js
@@ -0,0 +1,199 @@
+// Use proxy to get same origin URLs for tiles that don't support CORS.
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var map, cacheWrite, cacheRead1, cacheRead2;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.OSM("OpenStreetMap (CORS)", null, {
+ eventListeners: {
+ tileloaded: updateStatus,
+ loadend: detect
+ }
+ }),
+ new OpenLayers.Layer.WMS("OSGeo (same origin - proxied)", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ }, {
+ eventListeners: {
+ tileloaded: updateStatus
+ }
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ // try cache before loading from remote resource
+ cacheRead1 = new OpenLayers.Control.CacheRead({
+ eventListeners: {
+ activate: function() {
+ cacheRead2.deactivate();
+ }
+ }
+ });
+ // try loading from remote resource and fall back to cache
+ cacheRead2 = new OpenLayers.Control.CacheRead({
+ autoActivate: false,
+ fetchEvent: "tileerror",
+ eventListeners: {
+ activate: function() {
+ cacheRead1.deactivate();
+ }
+ }
+ });
+ cacheWrite = new OpenLayers.Control.CacheWrite({
+ imageFormat: "image/jpeg",
+ eventListeners: {
+ cachefull: function() {
+ if (seeding) {
+ stopSeeding();
+ }
+ status.innerHTML = "Cache full.";
+ }
+ }
+ });
+ var layerSwitcher = new OpenLayers.Control.LayerSwitcher();
+ map.addControls([cacheRead1, cacheRead2, cacheWrite, layerSwitcher]);
+ layerSwitcher.maximizeControl();
+
+
+
+ // add UI and behavior
+ var status = document.getElementById("status"),
+ hits = document.getElementById("hits"),
+ cacheHits = 0,
+ seeding = false;
+ var read = document.getElementById("read");
+ read.checked = true;
+ read.onclick = toggleRead;
+ var write = document.getElementById("write");
+ write.checked = false;
+ write.onclick = toggleWrite;
+ document.getElementById("clear").onclick = clearCache;
+ var tileloadstart = document.getElementById("tileloadstart");
+ tileloadstart.checked = "checked";
+ tileloadstart.onclick = setType;
+ document.getElementById("tileerror").onclick = setType;
+ document.getElementById("seed").onclick = startSeeding;
+
+ // detect what the browser supports
+ function detect(evt) {
+ // detection is only done once, so we remove the listener.
+ evt.object.events.unregister("loadend", null, detect);
+ var tile = map.baseLayer.grid[0][0];
+ try {
+ var canvasContext = tile.getCanvasContext();
+ if (canvasContext) {
+ // will throw an exception if CORS image requests are not supported
+ canvasContext.canvas.toDataURL();
+ } else {
+ status.innerHTML = "Canvas not supported. Try a different browser.";
+ }
+ } catch(e) {
+ // we remove the OSM layer if CORS image requests are not supported.
+ map.setBaseLayer(map.layers[1]);
+ evt.object.destroy();
+ layerSwitcher.destroy();
+ }
+ }
+
+ // update the number of cache hits and detect missing CORS support
+ function updateStatus(evt) {
+ if (window.localStorage) {
+ status.innerHTML = localStorage.length + " entries in cache.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ if (evt && evt.tile.url.substr(0, 5) === "data:") {
+ cacheHits++;
+ }
+ hits.innerHTML = cacheHits + " cache hits.";
+ }
+
+ // turn the cacheRead controls on and off
+ function toggleRead() {
+ if (!this.checked) {
+ cacheRead1.deactivate();
+ cacheRead2.deactivate();
+ } else {
+ setType();
+ }
+ }
+
+ // turn the cacheWrite control on and off
+ function toggleWrite() {
+ cacheWrite[cacheWrite.active ? "deactivate" : "activate"]();
+ }
+
+ // clear all tiles from the cache
+ function clearCache() {
+ OpenLayers.Control.CacheWrite.clearCache();
+ updateStatus();
+ }
+
+ // activate the cacheRead control that matches the desired fetch strategy
+ function setType() {
+ if (tileloadstart.checked) {
+ cacheRead1.activate();
+ } else {
+ cacheRead2.activate();
+ }
+ }
+
+ // start seeding the cache
+ function startSeeding() {
+ var layer = map.baseLayer,
+ zoom = map.getZoom();
+ seeding = {
+ zoom: zoom,
+ extent: map.getExtent(),
+ center: map.getCenter(),
+ cacheWriteActive: cacheWrite.active,
+ buffer: layer.buffer,
+ layer: layer
+ };
+ // make sure the next setCenter triggers a load
+ map.zoomTo(zoom === layer.numZoomLevels-1 ? zoom - 1 : zoom + 1);
+ // turn on cache writing
+ cacheWrite.activate();
+ // turn off cache reading
+ cacheRead1.deactivate();
+ cacheRead2.deactivate();
+
+ layer.events.register("loadend", null, seed);
+
+ // start seeding
+ map.setCenter(seeding.center, zoom);
+ }
+
+ // seed a zoom level based on the extent at the time startSeeding was called
+ function seed() {
+ var layer = seeding.layer;
+ var tileWidth = layer.tileSize.w;
+ var nextZoom = map.getZoom() + 1;
+ var extentWidth = seeding.extent.getWidth() / map.getResolutionForZoom(nextZoom);
+ // adjust the layer's buffer size so we don't have to pan
+ layer.buffer = Math.ceil((extentWidth / tileWidth - map.getSize().w / tileWidth) / 2);
+ map.zoomIn();
+ if (nextZoom === layer.numZoomLevels-1) {
+ stopSeeding();
+ }
+ }
+
+ // stop seeding (when done or when cache is full)
+ function stopSeeding() {
+ // we're done - restore previous settings
+ seeding.layer.events.unregister("loadend", null, seed);
+ seeding.layer.buffer = seeding.buffer;
+ map.setCenter(seeding.center, seeding.zoom);
+ if (!seeding.cacheWriteActive) {
+ cacheWrite.deactivate();
+ }
+ if (read.checked) {
+ setType();
+ }
+ seeding = false;
+ }
+}
\ No newline at end of file
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index 5953534fc0..749a14f027 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -211,6 +211,8 @@
"OpenLayers/Control.js",
"OpenLayers/Control/Attribution.js",
"OpenLayers/Control/Button.js",
+ "OpenLayers/Control/CacheRead.js",
+ "OpenLayers/Control/CacheWrite.js",
"OpenLayers/Control/ZoomBox.js",
"OpenLayers/Control/ZoomToMaxExtent.js",
"OpenLayers/Control/DragPan.js",
diff --git a/lib/OpenLayers/Control/CacheRead.js b/lib/OpenLayers/Control/CacheRead.js
new file mode 100644
index 0000000000..2589fdf6ca
--- /dev/null
+++ b/lib/OpenLayers/Control/CacheRead.js
@@ -0,0 +1,156 @@
+/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheRead
+ * A control for using image tiles cached with
+ * from the browser's local storage.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Control.CacheRead = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: fetchEvent
+ * {String} The layer event to listen to for replacing remote resource tile
+ * URLs with cached data URIs. Supported values are "tileerror" (try
+ * remote first, fall back to cached) and "tileloadstart" (try cache
+ * first, fall back to remote). Default is "tileloadstart".
+ *
+ * Note that "tileerror" will not work for CORS enabled images (see
+ * https://developer.mozilla.org/en/CORS_Enabled_Image), i.e. layers
+ * configured with a in
+ * .
+ */
+ fetchEvent: "tileloadstart",
+
+ /**
+ * APIProperty: layers
+ * {Array()}. Optional. If provided, only these
+ * layers will receive tiles from the cache.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.CacheRead
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.register(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.unregister(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: fetch
+ * Listener to the event. Replaces a tile's url with a data
+ * URI from the cache.
+ *
+ * Parameters:
+ * evt - {Object} Event object with a tile property.
+ */
+ fetch: function(evt) {
+ if (this.active && window.localStorage &&
+ evt.tile instanceof OpenLayers.Tile.Image) {
+ var tile = evt.tile,
+ url = tile.url;
+ // deal with modified tile urls when both CacheWrite and CacheRead
+ // are active
+ if (!tile.layer.crossOriginKeyword && OpenLayers.ProxyHost &&
+ url.indexOf(OpenLayers.ProxyHost) === 0) {
+ url = OpenLayers.Control.CacheWrite.urlMap[url];
+ }
+ var dataURI = window.localStorage.getItem("olCache_" + tile.url);
+ if (dataURI) {
+ tile.url = dataURI;
+ if (evt.type === "tileerror") {
+ tile.setImgSrc(dataURI);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheRead"
+});
diff --git a/lib/OpenLayers/Control/CacheWrite.js b/lib/OpenLayers/Control/CacheWrite.js
new file mode 100644
index 0000000000..85ece0b1dd
--- /dev/null
+++ b/lib/OpenLayers/Control/CacheWrite.js
@@ -0,0 +1,243 @@
+/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheWrite
+ * A control for caching image tiles in the browser's local storage. The
+ * control is used to fetch and use the cached
+ * tile images.
+ *
+ * Note: Before using this control on any layer that is not your own, make sure
+ * that the terms of service of the tile provider allow local storage of tiles.
+ *
+ * Inherits from:
+ * -
+ */
+OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * To register events in the constructor, configure .
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from ):
+ * cachefull - Triggered when the cache is full. Listeners receive an
+ * object with a tile property as first argument. The tile references
+ * the tile that couldn't be cached.
+ */
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} Object with event listeners, keyed by event name. An optional
+ * scope property defines the scope that listeners will be executed in.
+ */
+
+ /**
+ * APIProperty: layers
+ * {Array()}. Optional. If provided, caching
+ * will be enabled for these layers only, otherwise for all cacheable
+ * layers.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: imageFormat
+ * {String} The image format used for caching. The default is "image/png".
+ * Supported formats depend on the user agent. If an unsupported
+ * is provided, "image/png" will be used. For aerial
+ * imagery, "image/jpeg" is recommended.
+ */
+ imageFormat: "image/png",
+
+ /**
+ * Property: quotaRegEx
+ * {RegExp}
+ */
+ quotaRegEx: (/quota/i),
+
+ /**
+ * Constructor: OpenLayers.Control.CacheWrite
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control.
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.on({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.cache,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.un({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.cache,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: makeSameOrigin
+ * If the tile does not have CORS image loading enabled and is from a
+ * different origin, use OpenLayers.ProxyHost to make it a same origin url.
+ *
+ * Parameters:
+ * evt - {}
+ */
+ makeSameOrigin: function(evt) {
+ if (this.active) {
+ var tile = evt.tile;
+ if (tile instanceof OpenLayers.Tile.Image &&
+ !tile.crossOriginKeyword &&
+ tile.url.substr(0, 5) !== "data:") {
+ var sameOriginUrl = OpenLayers.Request.makeSameOrigin(
+ tile.url, OpenLayers.ProxyHost
+ );
+ OpenLayers.Control.CacheWrite.urlMap[sameOriginUrl] = tile.url;
+ tile.url = sameOriginUrl;
+ }
+ }
+ },
+
+ /**
+ * Method: cache
+ * Adds a tile to the cache. When the cache is full, the "cachefull" event
+ * is triggered.
+ *
+ * Parameters:
+ * obj - {Object} Object with a tile property, tile being the
+ * with the data to add to the cache
+ */
+ cache: function(obj) {
+ if (this.active && window.localStorage) {
+ var tile = obj.tile;
+ if (tile instanceof OpenLayers.Tile.Image &&
+ tile.url.substr(0, 5) !== 'data:') {
+ try {
+ var canvasContext = tile.getCanvasContext();
+ if (canvasContext) {
+ window.localStorage.setItem(
+ "olCache_" + OpenLayers.Control.CacheWrite.urlMap[tile.url],
+ canvasContext.canvas.toDataURL(this.imageFormat)
+ );
+ delete OpenLayers.Control.CacheWrite.urlMap[tile.url];
+ }
+ } catch(e) {
+ // local storage full or CORS violation
+ var reason = e.name || e.message;
+ if (reason && this.quotaRegEx.test(reason)) {
+ this.events.triggerEvent("cachefull", {tile: tile});
+ } else {
+ OpenLayers.Console.error(e.toString());
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheWrite"
+});
+
+/**
+ * APIFunction: OpenLayers.Control.CacheWrite.clearCache
+ * Clears all tiles cached with from the cache.
+ */
+OpenLayers.Control.CacheWrite.clearCache = function() {
+ if (!window.localStorage) { return; }
+ var i, key;
+ for (i=window.localStorage.length-1; i>=0; --i) {
+ key = window.localStorage.key(i);
+ if (key.substr(0, 8) === "olCache_") {
+ window.localStorage.removeItem(key);
+ }
+ }
+};
+
+/**
+ * Property: OpenLayers.Control.CacheWrite.urlMap
+ * {Object} Mapping of same origin urls to cache url keys. Entries will be
+ * deleted as soon as a tile was cached.
+ */
+OpenLayers.Control.CacheWrite.urlMap = {};
+
+
diff --git a/lib/OpenLayers/Request.js b/lib/OpenLayers/Request.js
index a5de46fdee..86e15d8099 100644
--- a/lib/OpenLayers/Request.js
+++ b/lib/OpenLayers/Request.js
@@ -65,6 +65,47 @@ OpenLayers.Request = {
*/
events: new OpenLayers.Events(this),
+ /**
+ * Method: makeSameOrigin
+ * Using the specified proxy, returns a same origin url of the provided url.
+ *
+ * Parameters:
+ * url - {String} An arbitrary url
+ * proxy {String|Function} The proxy to use to make the provided url a
+ * same origin url.
+ *
+ * Returns
+ * {String} the same origin url. If no proxy is provided, the returned url
+ * will be the same as the provided url.
+ */
+ makeSameOrigin: function(url, proxy) {
+ var sameOrigin = !(url.indexOf("http") == 0);
+ var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
+ if (urlParts) {
+ var location = window.location;
+ sameOrigin =
+ urlParts[1] == location.protocol &&
+ urlParts[3] == location.hostname;
+ var uPort = urlParts[4], lPort = location.port;
+ if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
+ sameOrigin = sameOrigin && uPort == lPort;
+ }
+ }
+ if (!sameOrigin) {
+ if (proxy) {
+ if (typeof proxy == "function") {
+ url = proxy(url);
+ } else {
+ url = proxy + encodeURIComponent(url);
+ }
+ } else {
+ OpenLayers.Console.warn(
+ OpenLayers.i18n("proxyNeeded"), {url: url});
+ }
+ }
+ return url;
+ },
+
/**
* APIMethod: issue
* Create a new XMLHttpRequest object, open it, set any headers, bind
@@ -153,30 +194,7 @@ OpenLayers.Request = {
var request = new OpenLayers.Request.XMLHttpRequest();
var url = OpenLayers.Util.urlAppend(config.url,
OpenLayers.Util.getParameterString(config.params || {}));
- var sameOrigin = !(url.indexOf("http") == 0);
- var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
- if (urlParts) {
- var location = window.location;
- sameOrigin =
- urlParts[1] == location.protocol &&
- urlParts[3] == location.hostname;
- var uPort = urlParts[4], lPort = location.port;
- if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
- sameOrigin = sameOrigin && uPort == lPort;
- }
- }
- if (!sameOrigin) {
- if (config.proxy) {
- if (typeof config.proxy == "function") {
- url = config.proxy(url);
- } else {
- url = config.proxy + encodeURIComponent(url);
- }
- } else {
- OpenLayers.Console.warn(
- OpenLayers.i18n("proxyNeeded"), {url: url});
- }
- }
+ url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
request.open(
config.method, url, config.async, config.user, config.password
);
diff --git a/tests/Control/CacheRead.html b/tests/Control/CacheRead.html
new file mode 100644
index 0000000000..4755508c26
--- /dev/null
+++ b/tests/Control/CacheRead.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Control/CacheWrite.html b/tests/Control/CacheWrite.html
new file mode 100644
index 0000000000..fdb6cabeaf
--- /dev/null
+++ b/tests/Control/CacheWrite.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/list-tests.html b/tests/list-tests.html
index cef96a4dd7..973576590f 100644
--- a/tests/list-tests.html
+++ b/tests/list-tests.html
@@ -13,6 +13,8 @@