Merge pull request #1309 from samhed/disappearing_cursor

Fix disappearing cursor
This commit is contained in:
Samuel Mannehed 2019-10-21 14:16:48 +02:00 committed by GitHub
commit ffdd0dfeef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 45 deletions

View File

@ -132,7 +132,8 @@ export default class Cursor {
} }
_handleMouseLeave(event) { _handleMouseLeave(event) {
this._hideCursor(); // Check if we should show the cursor on the element we are leaving to
this._updateVisibility(event.relatedTarget);
} }
_handleMouseMove(event) { _handleMouseMove(event) {
@ -150,6 +151,25 @@ export default class Cursor {
// now and adjust visibility based on that. // now and adjust visibility based on that.
let target = document.elementFromPoint(event.clientX, event.clientY); let target = document.elementFromPoint(event.clientX, event.clientY);
this._updateVisibility(target); this._updateVisibility(target);
// Captures end with a mouseup but we can't know the event order of
// mouseup vs releaseCapture.
//
// In the cases when releaseCapture comes first, the code above is
// enough.
//
// In the cases when the mouseup comes first, we need wait for the
// browser to flush all events and then check again if the cursor
// should be visible.
if (this._captureIsActive()) {
window.setTimeout(() => {
// Refresh the target from elementFromPoint since queued events
// might have altered the DOM
target = document.elementFromPoint(event.clientX,
event.clientY);
this._updateVisibility(target);
}, 0);
}
} }
_handleTouchStart(event) { _handleTouchStart(event) {
@ -189,6 +209,9 @@ export default class Cursor {
// (i.e. are we over the target, or a child of the target without a // (i.e. are we over the target, or a child of the target without a
// different cursor set) // different cursor set)
_shouldShowCursor(target) { _shouldShowCursor(target) {
if (!target) {
return false;
}
// Easy case // Easy case
if (target === this._target) { if (target === this._target) {
return true; return true;
@ -207,6 +230,11 @@ export default class Cursor {
} }
_updateVisibility(target) { _updateVisibility(target) {
// When the cursor target has capture we want to show the cursor.
// So, if a capture is active - look at the captured element instead.
if (this._captureIsActive()) {
target = document.capturedElem;
}
if (this._shouldShowCursor(target)) { if (this._shouldShowCursor(target)) {
this._showCursor(); this._showCursor();
} else { } else {
@ -218,4 +246,9 @@ export default class Cursor {
this._canvas.style.left = this._position.x + "px"; this._canvas.style.left = this._position.x + "px";
this._canvas.style.top = this._position.y + "px"; this._canvas.style.top = this._position.y + "px";
} }
_captureIsActive() {
return document.capturedElem &&
document.documentElement.contains(document.capturedElem);
}
} }

View File

@ -21,7 +21,8 @@ export function stopEvent(e) {
// Emulate Element.setCapture() when not supported // Emulate Element.setCapture() when not supported
let _captureRecursion = false; let _captureRecursion = false;
let _captureElem = null; let _elementForUnflushedEvents = null;
document.capturedElem = null;
function _captureProxy(e) { function _captureProxy(e) {
// Recursion protection as we'll see our own event // Recursion protection as we'll see our own event
if (_captureRecursion) return; if (_captureRecursion) return;
@ -30,7 +31,11 @@ function _captureProxy(e) {
const newEv = new e.constructor(e.type, e); const newEv = new e.constructor(e.type, e);
_captureRecursion = true; _captureRecursion = true;
_captureElem.dispatchEvent(newEv); if (document.capturedElem) {
document.capturedElem.dispatchEvent(newEv);
} else {
_elementForUnflushedEvents.dispatchEvent(newEv);
}
_captureRecursion = false; _captureRecursion = false;
// Avoid double events // Avoid double events
@ -48,58 +53,56 @@ function _captureProxy(e) {
} }
// Follow cursor style of target element // Follow cursor style of target element
function _captureElemChanged() { function _capturedElemChanged() {
const captureElem = document.getElementById("noVNC_mouse_capture_elem"); const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor; proxyElem.style.cursor = window.getComputedStyle(document.capturedElem).cursor;
} }
const _captureObserver = new MutationObserver(_captureElemChanged); const _captureObserver = new MutationObserver(_capturedElemChanged);
let _captureIndex = 0; export function setCapture(target) {
if (target.setCapture) {
export function setCapture(elem) { target.setCapture();
if (elem.setCapture) { document.capturedElem = target;
elem.setCapture();
// IE releases capture on 'click' events which might not trigger // IE releases capture on 'click' events which might not trigger
elem.addEventListener('mouseup', releaseCapture); target.addEventListener('mouseup', releaseCapture);
} else { } else {
// Release any existing capture in case this method is // Release any existing capture in case this method is
// called multiple times without coordination // called multiple times without coordination
releaseCapture(); releaseCapture();
let captureElem = document.getElementById("noVNC_mouse_capture_elem"); let proxyElem = document.getElementById("noVNC_mouse_capture_elem");
if (captureElem === null) { if (proxyElem === null) {
captureElem = document.createElement("div"); proxyElem = document.createElement("div");
captureElem.id = "noVNC_mouse_capture_elem"; proxyElem.id = "noVNC_mouse_capture_elem";
captureElem.style.position = "fixed"; proxyElem.style.position = "fixed";
captureElem.style.top = "0px"; proxyElem.style.top = "0px";
captureElem.style.left = "0px"; proxyElem.style.left = "0px";
captureElem.style.width = "100%"; proxyElem.style.width = "100%";
captureElem.style.height = "100%"; proxyElem.style.height = "100%";
captureElem.style.zIndex = 10000; proxyElem.style.zIndex = 10000;
captureElem.style.display = "none"; proxyElem.style.display = "none";
document.body.appendChild(captureElem); document.body.appendChild(proxyElem);
// This is to make sure callers don't get confused by having // This is to make sure callers don't get confused by having
// our blocking element as the target // our blocking element as the target
captureElem.addEventListener('contextmenu', _captureProxy); proxyElem.addEventListener('contextmenu', _captureProxy);
captureElem.addEventListener('mousemove', _captureProxy); proxyElem.addEventListener('mousemove', _captureProxy);
captureElem.addEventListener('mouseup', _captureProxy); proxyElem.addEventListener('mouseup', _captureProxy);
} }
_captureElem = elem; document.capturedElem = target;
_captureIndex++;
// Track cursor and get initial cursor // Track cursor and get initial cursor
_captureObserver.observe(elem, {attributes: true}); _captureObserver.observe(target, {attributes: true});
_captureElemChanged(); _capturedElemChanged();
captureElem.style.display = ""; proxyElem.style.display = "";
// We listen to events on window in order to keep tracking if it // We listen to events on window in order to keep tracking if it
// happens to leave the viewport // happens to leave the viewport
@ -112,26 +115,26 @@ export function releaseCapture() {
if (document.releaseCapture) { if (document.releaseCapture) {
document.releaseCapture(); document.releaseCapture();
document.capturedElem = null;
} else { } else {
if (!_captureElem) { if (!document.capturedElem) {
return; return;
} }
// There might be events already queued, so we need to wait for // There might be events already queued. The event proxy needs
// them to flush. E.g. contextmenu in Microsoft Edge // access to the captured element for these queued events.
window.setTimeout((expected) => { // E.g. contextmenu (right-click) in Microsoft Edge
// Only clear it if it's the expected grab (i.e. no one //
// else has initiated a new grab) // Before removing the capturedElem pointer we save it to a
if (_captureIndex === expected) { // temporary variable that the unflushed events can use.
_captureElem = null; _elementForUnflushedEvents = document.capturedElem;
} document.capturedElem = null;
}, 0, _captureIndex);
_captureObserver.disconnect(); _captureObserver.disconnect();
const captureElem = document.getElementById("noVNC_mouse_capture_elem"); const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.display = "none"; proxyElem.style.display = "none";
window.removeEventListener('mousemove', _captureProxy); window.removeEventListener('mousemove', _captureProxy);
window.removeEventListener('mouseup', _captureProxy); window.removeEventListener('mouseup', _captureProxy);