diff --git a/changelog.txt b/changelog.txt index 33787519..e77bb84d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,6 +18,8 @@ OPENSEADRAGON CHANGELOG * Viewport.setRotation now allows all rotation angles (#466) * Pinch rotate is now available (defaults to off) (#468) * Added option for home button to fill viewer (#474) +* Now handling iframe/frame mouseouts properly (#481) +* Better handling of mid-update image loaded callbacks (#409) 1.1.1: diff --git a/src/drawer.js b/src/drawer.js index 34cf3506..be1438f3 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -49,7 +49,7 @@ var DEVICE_SCREEN = $.getWindowSize(), /** * @class Drawer - * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. + * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}). * * @memberof OpenSeadragon @@ -730,23 +730,10 @@ function loadTile( drawer, tile, time ) { } function onTileLoad( drawer, tile, time, image ) { - var insertionIndex, - cutoff, - worstTile, - worstTime, - worstLevel, - worstTileIndex, - prevTile, - prevTime, - prevLevel, - i; tile.loading = false; - if ( drawer.midUpdate ) { - $.console.warn( "Tile load callback in middle of drawing routine." ); - return; - } else if ( !image && !drawer.viewport.collectionMode ) { + if ( !image && !drawer.viewport.collectionMode ) { $.console.log( "Tile %s failed to load: %s", tile, tile.url ); if( !drawer.debugMode ){ tile.exists = false; @@ -760,47 +747,64 @@ function onTileLoad( drawer, tile, time, image ) { tile.loaded = true; tile.image = image; - insertionIndex = drawer.tilesLoaded.length; - - if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) { - cutoff = Math.ceil( Math.log( drawer.source.getTileSize(tile.level) ) / Math.log( 2 ) ); - - worstTile = null; - worstTileIndex = -1; - - for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { - prevTile = drawer.tilesLoaded[ i ]; - - if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { - continue; - } else if ( !worstTile ) { - worstTile = prevTile; - worstTileIndex = i; - continue; - } - - prevTime = prevTile.lastTouchTime; - worstTime = worstTile.lastTouchTime; - prevLevel = prevTile.level; - worstLevel = worstTile.level; - - if ( prevTime < worstTime || - ( prevTime == worstTime && prevLevel > worstLevel ) ) { - worstTile = prevTile; - worstTileIndex = i; - } + if ( drawer.tilesLoaded.length < drawer.maxImageCacheCount ) { + // always safe to append things to cache + drawer.tilesLoaded[ drawer.tilesLoaded.length ] = tile; + } + else { + // need to remove something from cache, + // make sure this doesn't happen mid update + if ( !drawer.midUpdate ) { + updateTileCache( tile, drawer ); } - - if ( worstTile && worstTileIndex >= 0 ) { - worstTile.unload(); - insertionIndex = worstTileIndex; + else { + window.setTimeout( function() { + updateTileCache( tile, drawer ); + }, 1); } } - drawer.tilesLoaded[ insertionIndex ] = tile; drawer.updateAgain = true; } +function updateTileCache( newTile, drawer ) { + var i, prevTile, prevTime, worstTime, prevLevel, worstLevel, + insertionIndex = drawer.tilesLoaded.length, + cutoff = Math.ceil( Math.log( drawer.source.getTileSize(newTile.level) ) / Math.log( 2 ) ), + worstTile = null, + worstTileIndex = -1; + + for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { + prevTile = drawer.tilesLoaded[ i ]; + + if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { + continue; + } else if ( !worstTile ) { + worstTile = prevTile; + worstTileIndex = i; + continue; + } + + prevTime = prevTile.lastTouchTime; + worstTime = worstTile.lastTouchTime; + prevLevel = prevTile.level; + worstLevel = worstTile.level; + + if ( prevTime < worstTime || + ( prevTime == worstTime && prevLevel > worstLevel ) ) { + worstTile = prevTile; + worstTileIndex = i; + } + } + + if ( worstTile && worstTileIndex >= 0 ) { + worstTile.unload(); + insertionIndex = worstTileIndex; + } + + drawer.tilesLoaded[ insertionIndex ] = newTile; +} + function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility ){ var boundsTL = tile.bounds.getTopLeft(), diff --git a/src/imageLoader.js b/src/imageLoader.js index 0f79546c..9426fefb 100644 --- a/src/imageLoader.js +++ b/src/imageLoader.js @@ -145,7 +145,14 @@ $.ImageLoader.prototype = { else { this.jobQueue.push( newJob ); } + }, + /** + * Clear any unstarted image loading jobs from the queue. + * @method + */ + clear: function() { + this.jobQueue = []; } }; diff --git a/src/mousetracker.js b/src/mousetracker.js index 7a4df604..0c865c74 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -193,6 +193,8 @@ mousemove: function ( event ) { onMouseMove( _this, event ); }, mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, + mouseoutdocument: function ( event ) { onMouseOutDocument( _this, event ); }, + touchenter: function ( event ) { onTouchEnter( _this, event ); }, touchleave: function ( event ) { onTouchLeave( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, @@ -207,6 +209,10 @@ MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, + + pointeroutdocument: function ( event ) { onPointerOutDocument( _this, event ); }, + MSPointerOutdocument: function ( event ) { onPointerOutDocument( _this, event ); }, + pointerdown: function ( event ) { onPointerDown( _this, event ); }, MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, @@ -1049,6 +1055,16 @@ false ); } + + // handle pointer/mouse out of document body + if ( window.PointerEvent ) { + $.addEvent(document.body, "pointerout", delegate.pointeroutdocument); + } else if ( window.MSPointerEvent ) { + $.addEvent(document.body, "pointerout", delegate.MSPointerOutdocument); + } else { + $.addEvent(document.body, "mouseout", delegate.mouseoutdocument); + } + delegate.tracking = true; } } @@ -1074,6 +1090,15 @@ ); } + // handle pointer/mouse out of document body + if ( window.PointerEvent ) { + $.removeEvent(document.body, "pointerout", delegate.pointeroutdocument); + } else if ( window.MSPointerEvent ) { + $.removeEvent(document.body, "MSPointerOut", delegate.MSPointerOutdocument); + } else { + $.removeEvent(document.body, "mouseout", delegate.mouseoutdocument); + } + delegate.tracking = false; } } @@ -1456,6 +1481,35 @@ updatePointersExit( tracker, event, [ gPoint ] ); } + /** + * This handler is used to handle the case where the mouse is dragged out of the window, it should cause the drag to be properly released. + * + * @private + * @inner + */ + function onMouseOutDocument( tracker, event ) { + event = $.getEvent( event ); + + var html = document.getElementsByTagName("html")[0]; + var target = event.target || event.srcElement; + if ((event.relatedTarget!==html && event.relatedTarget!==null) || event.currentTarget !== document.body) { + return; // not a mouseout of the iframe + } + + var gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse', + isPrimary: true, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + event.buttons = undefined; + + if ( updatePointersUp( tracker, event, [ gPoint ], 0 ) ) { + releasePointer( tracker, true ); + } + } /** * @private @@ -1528,7 +1582,6 @@ } } - /** * @private * @inner @@ -1800,6 +1853,32 @@ updatePointersExit( tracker, event, [ gPoint ] ); } + /** + * This handler is used to handle the case where the pointer is dragged out of the window, it should cause the drag to be properly released. + * + * @private + * @inner + */ + function onPointerOutDocument( tracker, event ) { + event = $.getEvent( event ); + + var html = document.getElementsByTagName("html")[0]; + if ((event.relatedTarget!==html && event.relatedTarget!==null) || event.currentTarget !== document.body) { + return; // not a mouseout of the iframe + } + + var gPoint = { + id: event.pointerId, + type: getPointerType( event ), + isPrimary: event.isPrimary, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + if ( updatePointersUp( tracker, event, [ gPoint ], 0 ) ) { + releasePointer( tracker, false ); + } + } /** * @private diff --git a/test/events.js b/test/events.js index 36a49b17..ba1a36e2 100644 --- a/test/events.js +++ b/test/events.js @@ -165,6 +165,13 @@ var simulateLeave = function (x, y) { simEvent.clientX = offset.left + x; simEvent.clientY = offset.top + y; + simEvent.relatedTarget = document.body; + $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); + }; + var simulateLeaveFrame = function (x, y) { + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + simEvent.relatedTarget = document.getElementsByTagName("html")[0]; $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); }; @@ -446,6 +453,32 @@ quickClick: false }); + + // enter-press-move-exit-move-release-outside (drag, release outside iframe) + resetForAssessment(); + simulateEnter(0, 0); + simulateDown(0, 0); + simulateMove(1, 1, 5); + simulateMove(-1, -1, 5); + simulateLeaveFrame(-1, -1); + // you don't actually receive the mouseup if you mouseup outside of the document + assessGestureExpectations({ + description: 'enter-press-move-exit-move-release-outside (drag, release outside iframe): ', + enterCount: 1, + exitCount: 1, + pressCount: 1, + releaseCount: 1, + moveCount: 10, + clickCount: 0, + dblClickCount: 0, + dragCount: 10, + dragEndCount: 1, + insideElementPressed: true, + insideElementReleased: false, + contacts: 0, + trackedPointers: 0, + quickClick: false + }); unhookViewerHandlers(); viewer.close(); @@ -590,7 +623,7 @@ var checkOriginalEventReceivedViewer = function ( event ) { eventsHandledViewer++; - //TODO Provide a better check for the original event...simulate doesn't currently extend the object + //TODO Provide a better check for the original event...simulate doesn't currently extend the object // with arbitrary user data. if ( event && event.originalEvent ) { originalEventsPassedViewer++; @@ -622,7 +655,7 @@ if ( event && event.eventSource === mouseTracker ) { eventSourcePassedMouseTracker++; } - //TODO Provide a better check for the original event...simulate doesn't currently extend the object + //TODO Provide a better check for the original event...simulate doesn't currently extend the object // with arbitrary user data. if ( event && event.originalEvent ) { originalEventsPassedMouseTracker++;