From 2a8cb43d020f0422bc00b294bcff088ee49fb3cd Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Fri, 12 Sep 2014 16:01:19 -0700
Subject: [PATCH 1/7] Fixes issue #464 by adding mouseout handler to document
 and calling mouseUp handler

---
 src/mousetracker.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/mousetracker.js b/src/mousetracker.js
index 7a4df604..60500660 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -192,6 +192,8 @@
             mouseupcaptured:       function ( event ) { onMouseUpCaptured( _this, event ); },
             mousemove:             function ( event ) { onMouseMove( _this, event ); },
             mousemovecaptured:     function ( event ) { onMouseMoveCaptured( _this, event ); },
+            
+            mouseoutdocument:      function ( event ) { onMouseUp( _this, event ); },
 
             touchenter:            function ( event ) { onTouchEnter( _this, event ); },
             touchleave:            function ( event ) { onTouchLeave( _this, event ); },
@@ -1049,6 +1051,10 @@
                     false
                 );
             }
+
+            // handle mouse out of document area
+            $.addEvent(document, "mouseout",  delegate['mouseoutdocument']);
+
             delegate.tracking = true;
         }
     }
@@ -1074,6 +1080,9 @@
                 );
             }
 
+            // handle mouse out of document area
+            $.removeEvent(document, "mouseout",  delegate['mouseoutdocument']);
+
             delegate.tracking = false;
         }
     }

From 7278793e5cca32f90472e1fec0a71dbc4b11c848 Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Fri, 12 Sep 2014 16:22:55 -0700
Subject: [PATCH 2/7] Fixed notation for travis build.

---
 src/mousetracker.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/mousetracker.js b/src/mousetracker.js
index 60500660..eb1fb94f 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -192,7 +192,7 @@
             mouseupcaptured:       function ( event ) { onMouseUpCaptured( _this, event ); },
             mousemove:             function ( event ) { onMouseMove( _this, event ); },
             mousemovecaptured:     function ( event ) { onMouseMoveCaptured( _this, event ); },
-            
+
             mouseoutdocument:      function ( event ) { onMouseUp( _this, event ); },
 
             touchenter:            function ( event ) { onTouchEnter( _this, event ); },
@@ -1053,7 +1053,7 @@
             }
 
             // handle mouse out of document area
-            $.addEvent(document, "mouseout",  delegate['mouseoutdocument']);
+            $.addEvent(document, "mouseout",  delegate.mouseoutdocument);
 
             delegate.tracking = true;
         }
@@ -1081,7 +1081,7 @@
             }
 
             // handle mouse out of document area
-            $.removeEvent(document, "mouseout",  delegate['mouseoutdocument']);
+            $.removeEvent(document, "mouseout",  delegate.mouseoutdocument);
 
             delegate.tracking = false;
         }

From f07108ec2d2c0a160a75489dd24cab977a386604 Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Tue, 23 Sep 2014 11:26:42 -0700
Subject: [PATCH 3/7] Updated implementation to better pass tests, still not
 100% though.

---
 src/mousetracker.js | 17 ++++++++++++++---
 test/events.js      | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/src/mousetracker.js b/src/mousetracker.js
index eb1fb94f..6f58ffa5 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -193,7 +193,7 @@
             mousemove:             function ( event ) { onMouseMove( _this, event ); },
             mousemovecaptured:     function ( event ) { onMouseMoveCaptured( _this, event ); },
 
-            mouseoutdocument:      function ( event ) { onMouseUp( _this, event ); },
+            mouseoutdocument:      function ( event ) { onMouseOutDocument( _this, event ); },
 
             touchenter:            function ( event ) { onTouchEnter( _this, event ); },
             touchleave:            function ( event ) { onTouchLeave( _this, event ); },
@@ -1053,7 +1053,7 @@
             }
 
             // handle mouse out of document area
-            $.addEvent(document, "mouseout",  delegate.mouseoutdocument);
+            $.addEvent(document.body, "mouseout",  delegate.mouseoutdocument);
 
             delegate.tracking = true;
         }
@@ -1081,7 +1081,7 @@
             }
 
             // handle mouse out of document area
-            $.removeEvent(document, "mouseout",  delegate.mouseoutdocument);
+            $.removeEvent(document.body, "mouseout",  delegate.mouseoutdocument);
 
             delegate.tracking = false;
         }
@@ -1537,6 +1537,17 @@
         }
     }
 
+    function onMouseOutDocument( 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
+        }
+        event.buttons = undefined;
+        event.button = 0;
+        handleMouseUp(tracker,event);
+    }
 
     /**
      * @private
diff --git a/test/events.js b/test/events.js
index 36a49b17..ac796cd8 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,33 @@
                 quickClick:            false
             });
 
+
+            // enter-press-move-exit-move-release (drag, release outside tracked element)
+            resetForAssessment();
+            simulateEnter(0, 0);
+            simulateDown(0, 0);
+            simulateMove(1, 1, 5);
+            simulateMove(105, 105, 5);
+            simulateLeaveFrame(105, 105);
+            simulateMove(105, 105, 5);
+            // 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:             15,
+                clickCount:            0,
+                dblClickCount:         0,
+                dragCount:             10,
+                dragEndCount:          1,
+                insideElementPressed:  true,
+                insideElementReleased: false,
+                contacts:              0,
+                trackedPointers:       0,
+                quickClick:            false
+            });
             unhookViewerHandlers();
 
             viewer.close();

From 23c9155e525178a3e9854ed3cbc9cda587fa637f Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Tue, 23 Sep 2014 11:30:46 -0700
Subject: [PATCH 4/7] Fixed comment.

---
 test/events.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/events.js b/test/events.js
index ac796cd8..f72e02e3 100644
--- a/test/events.js
+++ b/test/events.js
@@ -454,7 +454,7 @@
             });
 
 
-            // enter-press-move-exit-move-release (drag, release outside tracked element)
+            // enter-press-move-exit-move-release-outside (drag, release outside iframe)
             resetForAssessment();
             simulateEnter(0, 0);
             simulateDown(0, 0);

From bbcb9c1219c95b0feea446926e10e88ade0c6193 Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Tue, 23 Sep 2014 11:32:37 -0700
Subject: [PATCH 5/7] Fixed bounds of test.

---
 test/events.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/events.js b/test/events.js
index f72e02e3..9b58b9e2 100644
--- a/test/events.js
+++ b/test/events.js
@@ -459,9 +459,9 @@
             simulateEnter(0, 0);
             simulateDown(0, 0);
             simulateMove(1, 1, 5);
-            simulateMove(105, 105, 5);
-            simulateLeaveFrame(105, 105);
-            simulateMove(105, 105, 5);
+            simulateMove(-1, -1, 5);
+            simulateLeaveFrame(-1, -1);
+            simulateMove(-1, -1, 5);
             // 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):  ',

From 1d1b9bf01af28a84b2af978de199289e9f1e0818 Mon Sep 17 00:00:00 2001
From: Ben Delarre <ben@delarre.net>
Date: Tue, 23 Sep 2014 14:31:53 -0700
Subject: [PATCH 6/7] Added support for pointer API so this now works in IE11.

---
 src/mousetracker.js | 93 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 76 insertions(+), 17 deletions(-)

diff --git a/src/mousetracker.js b/src/mousetracker.js
index 6f58ffa5..0c865c74 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -209,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 ); },
@@ -1051,9 +1055,15 @@
                     false
                 );
             }
-
-            // handle mouse out of document area
-            $.addEvent(document.body, "mouseout",  delegate.mouseoutdocument);
+            
+            // 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;
         }
@@ -1080,8 +1090,14 @@
                 );
             }
 
-            // handle mouse out of document area
-            $.removeEvent(document.body, "mouseout",  delegate.mouseoutdocument);
+            // 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;
         }
@@ -1465,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
@@ -1537,18 +1582,6 @@
         }
     }
 
-    function onMouseOutDocument( 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
-        }
-        event.buttons = undefined;
-        event.button = 0;
-        handleMouseUp(tracker,event);
-    }
-
     /**
      * @private
      * @inner
@@ -1820,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

From 1366bb7abeec157dd300446ded46e780ec3f2236 Mon Sep 17 00:00:00 2001
From: Ian Gilman <ian@iangilman.com>
Date: Fri, 7 Nov 2014 16:07:04 -0800
Subject: [PATCH 7/7] Touch-ups for #481

---
 changelog.txt  | 1 +
 test/events.js | 7 +++----
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/changelog.txt b/changelog.txt
index 33787519..a1166a8d 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -18,6 +18,7 @@ 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)
 
 1.1.1:
 
diff --git a/test/events.js b/test/events.js
index 9b58b9e2..ba1a36e2 100644
--- a/test/events.js
+++ b/test/events.js
@@ -461,7 +461,6 @@
             simulateMove(1, 1, 5);
             simulateMove(-1, -1, 5);
             simulateLeaveFrame(-1, -1);
-            simulateMove(-1, -1, 5);
             // 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):  ',
@@ -469,7 +468,7 @@
                 exitCount:             1,
                 pressCount:            1,
                 releaseCount:          1,
-                moveCount:             15,
+                moveCount:             10,
                 clickCount:            0,
                 dblClickCount:         0,
                 dragCount:             10,
@@ -624,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++;
@@ -656,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++;