Add options for loading tiles via AJAX and custom AJAX request headers.

This commit is contained in:
Sean Nichols 2016-10-21 17:28:12 -04:00 committed by Sean Nichols
parent b941864ac5
commit ddab768696
11 changed files with 349 additions and 60 deletions

View File

@ -32,15 +32,26 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
(function( $ ){ (function($){
// private class /**
function ImageJob ( options ) { * @private
* @class ImageJob
* @classdesc Handles downloading of a single image.
* @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download.
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.headers] - Headers to add to the image request.
* @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
*/
function ImageJob (options) {
$.extend( true, this, { $.extend(true, this, {
timeout: $.DEFAULT_SETTINGS.timeout, timeout: $.DEFAULT_SETTINGS.timeout,
jobId: null jobId: null
}, options ); }, options);
/** /**
* Image object which will contain downloaded image. * Image object which will contain downloaded image.
@ -52,42 +63,86 @@ function ImageJob ( options ) {
ImageJob.prototype = { ImageJob.prototype = {
errorMsg: null, errorMsg: null,
/**
* Starts the image job.
* @method
*/
start: function(){ start: function(){
var _this = this; var self = this;
var selfAbort = this.abort;
this.image = new Image(); this.image = new Image();
if ( this.crossOriginPolicy !== false ) {
this.image.crossOrigin = this.crossOriginPolicy;
}
this.image.onload = function(){ this.image.onload = function(){
_this.finish( true ); self.finish(true);
}; };
this.image.onabort = this.image.onerror = function(){ this.image.onabort = this.image.onerror = function() {
_this.errorMsg = "Image load aborted"; self.errorMsg = "Image load aborted";
_this.finish( false ); self.finish(false);
}; };
this.jobId = window.setTimeout( function(){ this.jobId = window.setTimeout(function(){
_this.errorMsg = "Image load exceeded timeout"; self.errorMsg = "Image load exceeded timeout";
_this.finish( false ); self.finish(false);
}, this.timeout); }, this.timeout);
this.image.src = this.src; // Load the tile with an AJAX request if the loadWithAjax option is
// set. Otherwise load the image by setting the source proprety of the image object.
if (this.loadWithAjax) {
this.request = $.makeAjaxRequest({
url: this.src,
withCredentials: this.ajaxWithCredentials,
headers: this.headers,
responseType: "arraybuffer",
success: function(request) {
// Make the raw data into a blob
var blb = new window.Blob([request.response]);
// If the blob is empty for some reason consider the image load a failure.
if (blb.size === 0) {
self.errorMsg = "Empty image response.";
self.finish(false);
}
// Create a URL for the blob data and make it the source of the image object.
// This will still trigger Image.onload to indicate a successful tile load.
var url = (window.URL || window.webkitURL).createObjectURL(blb);
self.image.src = url;
},
error: function(request) {
self.errorMsg = "Image load aborted - XHR error";
self.finish(false);
}
});
// Provide a function to properly abort the request.
this.abort = function() {
self.request.abort();
// Call the existing abort function if available
if (typeof selfAbort === "function") {
selfAbort();
}
};
} else {
if (this.crossOriginPolicy !== false) {
this.image.crossOrigin = this.crossOriginPolicy;
}
this.image.src = this.src;
}
}, },
finish: function( successful ) { finish: function(successful) {
this.image.onload = this.image.onerror = this.image.onabort = null; this.image.onload = this.image.onerror = this.image.onabort = null;
if (!successful) { if (!successful) {
this.image = null; this.image = null;
} }
if ( this.jobId ) { if (this.jobId) {
window.clearTimeout( this.jobId ); window.clearTimeout(this.jobId);
} }
this.callback( this ); this.callback(this);
} }
}; };
@ -100,13 +155,13 @@ ImageJob.prototype = {
* @param {Object} options - Options for this ImageLoader. * @param {Object} options - Options for this ImageLoader.
* @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details. * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
*/ */
$.ImageLoader = function( options ) { $.ImageLoader = function(options) {
$.extend( true, this, { $.extend(true, this, {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit, jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
jobQueue: [], jobQueue: [],
jobsInProgress: 0 jobsInProgress: 0
}, options ); }, options);
}; };
@ -116,22 +171,31 @@ $.ImageLoader.prototype = {
/** /**
* Add an unloaded image to the loader queue. * Add an unloaded image to the loader queue.
* @method * @method
* @param {String} src - URL of image to download. * @param {Object} options - Options for this job.
* @param {String} crossOriginPolicy - CORS policy to use for downloads * @param {String} [options.src] - URL of image to download.
* @param {Function} callback - Called once image has been downloaded. * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.headers] - Headers to add to the image request.
* @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
* requests.
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
*/ */
addJob: function( options ) { addJob: function(options) {
var _this = this, var _this = this,
complete = function( job ) { complete = function(job) {
completeJob( _this, job, options.callback ); completeJob(_this, job, options.callback);
}, },
jobOptions = { jobOptions = {
src: options.src, src: options.src,
loadWithAjax: options.loadWithAjax,
headers: options.loadWithAjax ? options.headers : null,
crossOriginPolicy: options.crossOriginPolicy, crossOriginPolicy: options.crossOriginPolicy,
ajaxWithCredentials: options.ajaxWithCredentials,
callback: complete, callback: complete,
abort: options.abort abort: options.abort
}, },
newJob = new ImageJob( jobOptions ); newJob = new ImageJob(jobOptions);
if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) { if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
newJob.start(); newJob.start();
@ -166,18 +230,18 @@ $.ImageLoader.prototype = {
* @param job - The ImageJob that has completed. * @param job - The ImageJob that has completed.
* @param callback - Called once cleanup is finished. * @param callback - Called once cleanup is finished.
*/ */
function completeJob( loader, job, callback ) { function completeJob(loader, job, callback) {
var nextJob; var nextJob;
loader.jobsInProgress--; loader.jobsInProgress--;
if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) { if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
nextJob = loader.jobQueue.shift(); nextJob = loader.jobQueue.shift();
nextJob.start(); nextJob.start();
loader.jobsInProgress++; loader.jobsInProgress++;
} }
callback( job.image, job.errorMsg ); callback(job.image, job.errorMsg, job.request);
} }
}( OpenSeadragon )); }(OpenSeadragon));

View File

@ -584,9 +584,16 @@
* not use CORS, and the canvas will be tainted. * not use CORS, and the canvas will be tainted.
* *
* @property {Boolean} [ajaxWithCredentials=false] * @property {Boolean} [ajaxWithCredentials=false]
* Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources). * Whether to set the withCredentials XHR flag for AJAX requests.
* Note that this can be overridden at the {@link OpenSeadragon.TileSource} level. * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
* *
* @property {Boolean} [loadTilesWithAjax=false]
* Whether to load tile data using AJAX requests.
* Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
*
* @property {Object} [ajaxRequestHeaders={}]
* A set of headers to include when making AJAX requests for tile sources or tiles.
*
*/ */
/** /**
@ -1005,6 +1012,8 @@ function OpenSeadragon( options ){
initialPage: 0, initialPage: 0,
crossOriginPolicy: false, crossOriginPolicy: false,
ajaxWithCredentials: false, ajaxWithCredentials: false,
loadTilesWithAjax: false,
ajaxRequestHeaders: {},
//PAN AND ZOOM SETTINGS AND CONSTRAINTS //PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true, panHorizontal: true,
@ -2120,11 +2129,16 @@ function OpenSeadragon( options ){
* @param {String} options.url - the url to request * @param {String} options.url - the url to request
* @param {Function} options.success - a function to call on a successful response * @param {Function} options.success - a function to call on a successful response
* @param {Function} options.error - a function to call on when an error occurs * @param {Function} options.error - a function to call on when an error occurs
* @param {Object} options.headers - headers to add to the AJAX request
* @param {String} options.responseType - the response type of the the AJAX request
* @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
* @throws {Error} * @throws {Error}
* @returns {XMLHttpRequest}
*/ */
makeAjaxRequest: function( url, onSuccess, onError ) { makeAjaxRequest: function( url, onSuccess, onError ) {
var withCredentials; var withCredentials;
var headers;
var responseType;
// Note that our preferred API is that you pass in a single object; the named // Note that our preferred API is that you pass in a single object; the named
// arguments are for legacy support. // arguments are for legacy support.
@ -2132,6 +2146,8 @@ function OpenSeadragon( options ){
onSuccess = url.success; onSuccess = url.success;
onError = url.error; onError = url.error;
withCredentials = url.withCredentials; withCredentials = url.withCredentials;
headers = url.headers;
responseType = url.responseType || null;
url = url.url; url = url.url;
} }
@ -2147,9 +2163,9 @@ function OpenSeadragon( options ){
if ( request.readyState == 4 ) { if ( request.readyState == 4 ) {
request.onreadystatechange = function(){}; request.onreadystatechange = function(){};
// With protocols other than http/https, the status is 200 // With protocols other than http/https, the status is in [200, 300)
// on Firefox and 0 on other browsers // on Firefox and 0 on other browsers
if ( request.status === 200 || if ( (request.status >= 200 && request.status < 300) ||
( request.status === 0 && ( request.status === 0 &&
protocol !== "http:" && protocol !== "http:" &&
protocol !== "https:" )) { protocol !== "https:" )) {
@ -2167,11 +2183,21 @@ function OpenSeadragon( options ){
try { try {
request.open( "GET", url, true ); request.open( "GET", url, true );
if (responseType) {
request.responseType = responseType;
}
if (headers) {
Object.keys(headers).forEach(function (headerName) {
request.setRequestHeader(headerName, headers[headerName]);
});
}
if (withCredentials) { if (withCredentials) {
request.withCredentials = true; request.withCredentials = true;
} }
request.send( null ); request.send(null);
} catch (e) { } catch (e) {
var msg = e.message; var msg = e.message;
@ -2231,6 +2257,8 @@ function OpenSeadragon( options ){
} }
} }
} }
return request;
}, },
/** /**

View File

@ -47,8 +47,10 @@
* @param {String} url The URL of this tile's image. * @param {String} url The URL of this tile's image.
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
* is provided directly by the tile source. * is provided directly by the tile source.
* @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
* @param {Object} headers The headers to send with this tile's AJAX request (if applicable).
*/ */
$.Tile = function(level, x, y, bounds, exists, url, context2D) { $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, headers) {
/** /**
* The zoom level this tile belongs to. * The zoom level this tile belongs to.
* @member {Number} level * @member {Number} level
@ -91,6 +93,29 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D) {
* @memberOf OpenSeadragon.Tile# * @memberOf OpenSeadragon.Tile#
*/ */
this.context2D = context2D; this.context2D = context2D;
/**
* Whether to load this tile's image with an AJAX request.
* @member {Boolean} loadWithAjax
* @memberof OpenSeadragon.Tile#
*/
this.loadWithAjax = loadWithAjax;
/**
* The headers to be used in requesting this tile's image.
* Only used if loadWithAjax is set to true.
* @member {Object} headers
* @memberof OpenSeadragon.Tile#
*/
this.headers = headers;
/**
* The unique cache key for this tile.
* @member {String} cacheKey
* @memberof OpenSeadragon.Tile#
*/
if (this.headers) {
this.cacheKey = this.url + "+" + JSON.stringify(this.headers);
} else {
this.cacheKey = this.url;
}
/** /**
* Is this tile loaded? * Is this tile loaded?
* @member {Boolean} loaded * @member {Boolean} loaded

View File

@ -149,16 +149,16 @@ $.TileCache.prototype = {
cacheTile: function( options ) { cacheTile: function( options ) {
$.console.assert( options, "[TileCache.cacheTile] options is required" ); $.console.assert( options, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" ); $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
$.console.assert( options.tile.url, "[TileCache.cacheTile] options.tile.url is required" ); $.console.assert( options.tile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" ); $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0; var cutoff = options.cutoff || 0;
var insertionIndex = this._tilesLoaded.length; var insertionIndex = this._tilesLoaded.length;
var imageRecord = this._imagesLoaded[options.tile.url]; var imageRecord = this._imagesLoaded[options.tile.cacheKey];
if (!imageRecord) { if (!imageRecord) {
$.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" ); $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({ imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({
image: options.image image: options.image
}); });
@ -232,9 +232,9 @@ $.TileCache.prototype = {
}, },
// private // private
getImageRecord: function(url) { getImageRecord: function(cacheKey) {
$.console.assert(url, '[TileCache.getImageRecord] url is required'); $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');
return this._imagesLoaded[url]; return this._imagesLoaded[cacheKey];
}, },
// private // private
@ -246,11 +246,11 @@ $.TileCache.prototype = {
tile.unload(); tile.unload();
tile.cacheImageRecord = null; tile.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.url]; var imageRecord = this._imagesLoaded[tile.cacheKey];
imageRecord.removeTile(tile); imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) { if (!imageRecord.getTileCount()) {
imageRecord.destroy(); imageRecord.destroy();
delete this._imagesLoaded[tile.url]; delete this._imagesLoaded[tile.cacheKey];
this._imagesLoadedCount--; this._imagesLoadedCount--;
} }

View File

@ -75,6 +75,13 @@
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}. * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.loadTilesWithAjax]
* Whether to load tile data using AJAX requests.
* Defaults to the setting in {@link OpenSeadragon.Options}.
* @param {Object} [options.ajaxRequestHeaders={}]
* A set of headers to include when making tile AJAX requests.
* Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
*/ */
$.TiledImage = function( options ) { $.TiledImage = function( options ) {
var _this = this; var _this = this;
@ -161,6 +168,7 @@ $.TiledImage = function( options ) {
iOSDevice: $.DEFAULT_SETTINGS.iOSDevice, iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,
debugMode: $.DEFAULT_SETTINGS.debugMode, debugMode: $.DEFAULT_SETTINGS.debugMode,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity, opacity: $.DEFAULT_SETTINGS.opacity,
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
@ -1179,6 +1187,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
var tile = getTile( var tile = getTile(
x, y, x, y,
level, level,
tiledImage,
tiledImage.source, tiledImage.source,
tiledImage.tilesMatrix, tiledImage.tilesMatrix,
currentTime, currentTime,
@ -1237,7 +1246,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
if (tile.context2D) { if (tile.context2D) {
setTileLoaded(tiledImage, tile); setTileLoaded(tiledImage, tile);
} else { } else {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url); var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);
if (imageRecord) { if (imageRecord) {
var image = imageRecord.getImage(); var image = imageRecord.getImage();
setTileLoaded(tiledImage, tile, image); setTileLoaded(tiledImage, tile, image);
@ -1275,6 +1284,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
* @param {Number} x * @param {Number} x
* @param {Number} y * @param {Number} y
* @param {Number} level * @param {Number} level
* @param {OpenSeadragon.TiledImage} tiledImage
* @param {OpenSeadragon.TileSource} tileSource * @param {OpenSeadragon.TileSource} tileSource
* @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile. * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
* @param {Number} time * @param {Number} time
@ -1283,12 +1293,23 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
* @param {Number} worldHeight * @param {Number} worldHeight
* @returns {OpenSeadragon.Tile} * @returns {OpenSeadragon.Tile}
*/ */
function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) { function getTile(
x, y,
level,
tiledImage,
tileSource,
tilesMatrix,
time,
numTiles,
worldWidth,
worldHeight
) {
var xMod, var xMod,
yMod, yMod,
bounds, bounds,
exists, exists,
url, url,
headers,
context2D, context2D,
tile; tile;
@ -1305,6 +1326,18 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds = tileSource.getTileBounds( level, xMod, yMod ); bounds = tileSource.getTileBounds( level, xMod, yMod );
exists = tileSource.tileExists( level, xMod, yMod ); exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod ); url = tileSource.getTileUrl( level, xMod, yMod );
// Headers are only applicable if loadTilesWithAjax is set
if (tiledImage.loadTilesWithAjax) {
headers = tileSource.getTileHeaders( level, xMod, yMod );
// Combine tile headers with global headers (if applicable)
if ($.isPlainObject(tiledImage.ajaxRequestHeaders)) {
headers = $.extend({}, tiledImage.ajaxRequestHeaders, headers);
}
} else {
headers = null;
}
context2D = tileSource.getContext2D ? context2D = tileSource.getContext2D ?
tileSource.getContext2D(level, xMod, yMod) : undefined; tileSource.getContext2D(level, xMod, yMod) : undefined;
@ -1318,7 +1351,9 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds, bounds,
exists, exists,
url, url,
context2D context2D,
tiledImage.loadTilesWithAjax,
headers
); );
} }
@ -1340,9 +1375,12 @@ function loadTile( tiledImage, tile, time ) {
tile.loading = true; tile.loading = true;
tiledImage._imageLoader.addJob({ tiledImage._imageLoader.addJob({
src: tile.url, src: tile.url,
loadWithAjax: tile.loadWithAjax,
headers: tile.headers,
crossOriginPolicy: tiledImage.crossOriginPolicy, crossOriginPolicy: tiledImage.crossOriginPolicy,
callback: function( image, errorMsg ){ ajaxWithCredentials: tiledImage.ajaxWithCredentials,
onTileLoad( tiledImage, tile, time, image, errorMsg ); callback: function( image, errorMsg, tileRequest ){
onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );
}, },
abort: function() { abort: function() {
tile.loading = false; tile.loading = false;
@ -1359,8 +1397,9 @@ function loadTile( tiledImage, tile, time ) {
* @param {Number} time * @param {Number} time
* @param {Image} image * @param {Image} image
* @param {String} errorMsg * @param {String} errorMsg
* @param {XMLHttpRequest} tileRequest
*/ */
function onTileLoad( tiledImage, tile, time, image, errorMsg ) { function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {
if ( !image ) { if ( !image ) {
$.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
/** /**
@ -1373,8 +1412,15 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.
* @property {number} time - The time in milliseconds when the tile load began. * @property {number} time - The time in milliseconds when the tile load began.
* @property {string} message - The error message. * @property {string} message - The error message.
* @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.
*/ */
tiledImage.viewer.raiseEvent("tile-load-failed", {tile: tile, tiledImage: tiledImage, time: time, message: errorMsg}); tiledImage.viewer.raiseEvent("tile-load-failed", {
tile: tile,
tiledImage: tiledImage,
time: time,
message: errorMsg,
tileRequest: tileRequest
});
tile.loading = false; tile.loading = false;
tile.exists = false; tile.exists = false;
return; return;

View File

@ -65,6 +65,8 @@
* @param {Boolean} [options.ajaxWithCredentials] * @param {Boolean} [options.ajaxWithCredentials]
* If this TileSource needs to make an AJAX call, this specifies whether to set * If this TileSource needs to make an AJAX call, this specifies whether to set
* the XHR's withCredentials (for accessing secure data). * the XHR's withCredentials (for accessing secure data).
* @param {Object} [options.ajaxRequestHeaders]
* A set of headers to include in AJAX requests.
* @param {Number} [options.width] * @param {Number} [options.width]
* Width of the source image at max resolution in pixels. * Width of the source image at max resolution in pixels.
* @param {Number} [options.height] * @param {Number} [options.height]
@ -475,6 +477,7 @@ $.TileSource.prototype = {
$.makeAjaxRequest( { $.makeAjaxRequest( {
url: url, url: url,
withCredentials: this.ajaxWithCredentials, withCredentials: this.ajaxWithCredentials,
headers: this.ajaxRequestHeaders,
success: function( xhr ) { success: function( xhr ) {
var data = processResponse( xhr ); var data = processResponse( xhr );
callback( data ); callback( data );
@ -559,7 +562,7 @@ $.TileSource.prototype = {
}, },
/** /**
* Responsible for retriving the url which will return an image for the * Responsible for retrieving the url which will return an image for the
* region specified by the given x, y, and level components. * region specified by the given x, y, and level components.
* This method is not implemented by this class other than to throw an Error * This method is not implemented by this class other than to throw an Error
* announcing you have to implement it. Because of the variety of tile * announcing you have to implement it. Because of the variety of tile
@ -575,6 +578,20 @@ $.TileSource.prototype = {
throw new Error( "Method not implemented." ); throw new Error( "Method not implemented." );
}, },
/**
* Responsible for retrieving the headers which will be attached to the image request for the
* region specified by the given x, y, and level components.
* This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true.
* @function
* @param {Number} level
* @param {Number} x
* @param {Number} y
* @returns {Object}
*/
getTileHeaders: function( level, x, y ) {
return {};
},
/** /**
* @function * @function
* @param {Number} level * @param {Number} level

View File

@ -1232,6 +1232,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {String} [options.compositeOperation] How the image is composited onto other images. * @param {String} [options.compositeOperation] How the image is composited onto other images.
* @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image, * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
* overriding viewer.crossOriginPolicy. * overriding viewer.crossOriginPolicy.
* @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
* @param {Boolean} [options.loadTilesWithAjax]
* Whether to load tile data using AJAX requests.
* Defaults to the setting in {@link OpenSeadragon.Options}.
* @param {Object} [options.ajaxRequestHeaders]
* A set of headers to include when making tile AJAX requests.
* Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
* requests.
* @param {Function} [options.success] A function that gets called when the image is * @param {Function} [options.success] A function that gets called when the image is
* successfully added. It's passed the event object which contains a single property: * successfully added. It's passed the event object which contains a single property:
* "item", the resulting TiledImage. * "item", the resulting TiledImage.
@ -1270,6 +1278,21 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if (options.crossOriginPolicy === undefined) { if (options.crossOriginPolicy === undefined) {
options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy; options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
} }
if (options.ajaxWithCredentials === undefined) {
options.ajaxWithCredentials = this.ajaxWithCredentials;
}
if (options.loadTilesWithAjax === undefined) {
options.loadTilesWithAjax = this.loadTilesWithAjax;
}
if (options.ajaxRequestHeaders === undefined) {
options.ajaxRequestHeaders = this.ajaxRequestHeaders;
} else if (
$.isPlainObject(options.ajaxRequestHeaders) &&
$.isPlainObject(this.ajaxRequestHeaders)
) {
options.ajaxRequestHeaders = $.extend({},
this.ajaxRequestHeaders, options.ajaxRequestHeaders);
}
var myQueueItem = { var myQueueItem = {
options: options options: options
@ -1384,6 +1407,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom, smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
iOSDevice: _this.iOSDevice, iOSDevice: _this.iOSDevice,
crossOriginPolicy: queueItem.options.crossOriginPolicy, crossOriginPolicy: queueItem.options.crossOriginPolicy,
ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
ajaxRequestHeaders: queueItem.options.ajaxRequestHeaders,
debugMode: _this.debugMode debugMode: _this.debugMode
}); });
@ -2156,6 +2182,7 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ? crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy, imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials, ajaxWithCredentials: viewer.ajaxWithCredentials,
ajaxRequestHeaders: viewer.ajaxRequestHeaders,
useCanvas: viewer.useCanvas, useCanvas: viewer.useCanvas,
success: function( event ) { success: function( event ) {
successCallback( event.tileSource ); successCallback( event.tileSource );

BIN
test/data/testpattern.blob Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

View File

@ -25,7 +25,8 @@
id: "contentDiv", id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/", prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi", tileSources: "../data/testpattern.dzi",
showNavigator:true showNavigator:true,
ajaxWithCredentials: true
}); });
</script> </script>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Custom Request Headers Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.osd-viewer {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<p>
Demo of how the loadTilesWithAjax and ajaxRequestHeaders options as well as the getTileHeaders() method on TileSource can be applied.
</p>
<p>
Examine the network requests in your browser developer tools to see the custom headers sent with each request.
</p>
<div id="contentDiv" class="osd-viewer"></div>
<script type="text/javascript">
// These values are generated by a script that concatenates all the tile files and records
// their byte ranges in a multi-dimensional array.
const tileManifest = {"tileRanges":[[[[0,3467]]],[[[3467,6954]]],[[[344916,348425]]],[[[348425,351948]]],[[[351948,355576]]],[[[355576,359520]]],[[[359520,364663]]],[[[364663,374196]]],[[[374196,407307]]],[[[407307,435465],[435465,463663]],[[463663,491839],[491839,520078]]],[[[6954,29582],[29582,50315],[50315,71936],[71936,92703]],[[92703,113385],[113385,133265],[133265,154763],[154763,175710]],[[175710,197306],[197306,218807],[218807,242177],[242177,263007]],[[263007,283790],[283790,304822],[304822,325691],[325691,344916]]]],"totalSize":520078}
// This tile source demonstrates how you can retrieve individual tiles from a single file
// using the Range header.
var myCustomTileSource = {
width: 1000,
height: 1000,
tileWidth: 254,
tileHeight: 254,
tileOverlap: 1,
maxLevel: 10,
minLevel: 0,
// The tile URL is always the same. Only the Range header changes
getTileUrl: function () {
return "/test/data/testpattern.blob";
},
// This method will send the appropriate range header for this tile based on the data
// in tileByteRanges.
getTileHeaders: function(level, x, y) {
return {
Range: "bytes=" + tileManifest.tileRanges[level][x][y].join("-") + "/" + tileManifest.totalSize
}
},
};
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
showNavigator: true,
loadTilesWithAjax: true,
ajaxRequestHeaders: {
// Example of using the viewer-level ajaxRequestHeaders option
// for providing an authentication token.
'Authentication': 'Bearer MY_AUTH_TOKEN'
}
});
viewer.addTiledImage({
// The headers specified here will be combined with those in the Viewer object (if any)
ajaxRequestHeaders: {
"X-My-TileSource-Header": "Something"
},
tileSource: myCustomTileSource
});
</script>
</body>
</html>

View File

@ -25,12 +25,14 @@
var fakeTile0 = { var fakeTile0 = {
url: 'foo.jpg', url: 'foo.jpg',
cacheKey: 'foo.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile1 = { var fakeTile1 = {
url: 'foo.jpg', url: 'foo.jpg',
cacheKey: 'foo.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
@ -74,18 +76,21 @@
var fakeTile0 = { var fakeTile0 = {
url: 'different.jpg', url: 'different.jpg',
cacheKey: 'different.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile1 = { var fakeTile1 = {
url: 'same.jpg', url: 'same.jpg',
cacheKey: 'same.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile2 = { var fakeTile2 = {
url: 'same.jpg', url: 'same.jpg',
cacheKey: 'same.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };