mirror of
https://github.com/novnc/noVNC.git
synced 2026-05-26 15:13:34 +00:00
Dispatch an event on each state transition inside the WakeLockManager. This gives the unit tests something to synchronise on, allowing us to write fast, flake-free tests.
200 lines
7.0 KiB
JavaScript
200 lines
7.0 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* Copyright (C) 2025 The noVNC authors
|
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
|
*
|
|
* Wrapper around the `navigator.wakeLock` api that handles reacquiring the
|
|
* lock on visiblility changes.
|
|
*
|
|
* The `acquire` and `release` methods may be called any number of times. The
|
|
* most recent call dictates the desired end-state (if `acquire` was most
|
|
* recently called, then we will try to acquire and hold the wake lock).
|
|
*/
|
|
|
|
import * as Log from '../core/util/logging.js';
|
|
|
|
const _STATES = {
|
|
/* No wake lock.
|
|
*
|
|
* Can transition to:
|
|
* - AWAITING_VISIBLE: `acquire` called when document is hidden.
|
|
* - ACQUIRING: `acquire` called.
|
|
* - ERROR: `acquired` called when the api is not available.
|
|
*/
|
|
RELEASED: 'released',
|
|
/* Wake lock requested, waiting for browser.
|
|
*
|
|
* Can transition to:
|
|
* - ACQUIRED: success
|
|
* - ACQUIRING_WANT_RELEASE: `release` called while waiting
|
|
* - ERROR
|
|
*/
|
|
ACQUIRING: 'acquiring',
|
|
/* Wake lock requested, release called, still waiting for browser.
|
|
*
|
|
* Can transition to:
|
|
* - ACQUIRING: `acquire` called (but promise has not resolved yet)
|
|
* - RELEASED: success
|
|
*/
|
|
ACQUIRING_WANT_RELEASE: 'releasing',
|
|
/* Wake lock held.
|
|
*
|
|
* Can transition to:
|
|
* - AWAITING_VISIBLE: wakelock lost due to visibility change
|
|
* - RELEASED: success
|
|
*/
|
|
ACQUIRED: 'acquired',
|
|
/* Caller wants wakelock, but we can not get it due to visibility.
|
|
*
|
|
* Can transition to:
|
|
* - ACQUIRING: document is now visible, attempting to get wakelock.
|
|
* - RELEASED: when release is called.
|
|
*/
|
|
AWAITING_VISIBLE: 'awaiting_visible',
|
|
/* An error has occurred.
|
|
*
|
|
* Can transition to:
|
|
* - RELEASED: will happen immediately.
|
|
*/
|
|
ERROR: 'error',
|
|
};
|
|
|
|
class TestOnlyWakeLockManagerStateChangeEvent extends Event {
|
|
constructor(oldState, newState) {
|
|
super("testOnlyStateChange");
|
|
this.oldState = oldState;
|
|
this.newState = newState;
|
|
}
|
|
}
|
|
|
|
export default class WakeLockManager extends EventTarget {
|
|
constructor() {
|
|
super();
|
|
|
|
this._state = _STATES.RELEASED;
|
|
this._wakelock = null;
|
|
|
|
this._eventHandlers = {
|
|
wakelockAcquired: this._wakelockAcquired.bind(this),
|
|
wakelockReleased: this._wakelockReleased.bind(this),
|
|
documentVisibilityChange: this._documentVisibilityChange.bind(this),
|
|
};
|
|
}
|
|
|
|
acquire() {
|
|
switch (this._state) {
|
|
case _STATES.ACQUIRING_WANT_RELEASE:
|
|
// We are currently waiting to acquire the wakelock. While
|
|
// waiting, `release()` was called. By transitioning back to
|
|
// ACQUIRING, we will keep the lock after we receive it.
|
|
this._transitionTo(_STATES.ACQUIRING);
|
|
break;
|
|
case _STATES.AWAITING_VISIBLE:
|
|
case _STATES.ACQUIRING:
|
|
case _STATES.ACQUIRED:
|
|
break;
|
|
case _STATES.ERROR:
|
|
case _STATES.RELEASED:
|
|
if (document.hidden) {
|
|
// We can not acquire the wakelock while the document is
|
|
// hidden (eg, not the active tab). Wait until it is
|
|
// visible, then acquire the wakelock.
|
|
this._awaitVisible();
|
|
break;
|
|
}
|
|
this._acquireWakelockNow();
|
|
break;
|
|
}
|
|
}
|
|
|
|
release() {
|
|
switch (this._state) {
|
|
case _STATES.ERROR:
|
|
case _STATES.RELEASED:
|
|
case _STATES.ACQUIRING_WANT_RELEASE:
|
|
break;
|
|
case _STATES.ACQUIRING:
|
|
// We are have requested (but not yet received) the wakelock.
|
|
// Give it up as soon as we acquire it.
|
|
this._transitionTo(_STATES.ACQUIRING_WANT_RELEASE);
|
|
break;
|
|
case _STATES.ACQUIRED:
|
|
// We remove the event listener first, as we don't want to be
|
|
// notified about this release (it is expected).
|
|
this._wakelock.removeEventListener("release", this._eventHandlers.wakelockReleased);
|
|
this._wakelock.release();
|
|
this._wakelock = null;
|
|
this._transitionTo(_STATES.RELEASED);
|
|
break;
|
|
case _STATES.AWAITING_VISIBLE:
|
|
// We don't currently have the lock, but are waiting for the
|
|
// document to become visible. By removing the event listener,
|
|
// we will not attempt to get the wakelock in the future.
|
|
document.removeEventListener("visibilitychange", this._eventHandlers.documentVisibilityChange);
|
|
this._transitionTo(_STATES.RELEASED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_transitionTo(newState) {
|
|
let oldState = this._state;
|
|
Log.Debug(`WakelockManager transitioning ${oldState} -> ${newState}`);
|
|
this._state = newState;
|
|
this.dispatchEvent(new TestOnlyWakeLockManagerStateChangeEvent(oldState, newState));
|
|
}
|
|
|
|
_awaitVisible() {
|
|
document.addEventListener("visibilitychange", this._eventHandlers.documentVisibilityChange);
|
|
this._transitionTo(_STATES.AWAITING_VISIBLE);
|
|
}
|
|
|
|
_acquireWakelockNow() {
|
|
if (!("wakeLock" in navigator)) {
|
|
Log.Warn("Unable to request wakeLock, Browser does not have wakeLock api");
|
|
this._transitionTo(_STATES.ERROR);
|
|
this._transitionTo(_STATES.RELEASED);
|
|
return;
|
|
}
|
|
navigator.wakeLock.request("screen")
|
|
.then(this._eventHandlers.wakelockAcquired)
|
|
.catch((err) => {
|
|
Log.Warn("Error occurred while acquiring wakelock: " + err);
|
|
this._transitionTo(_STATES.ERROR);
|
|
this._transitionTo(_STATES.RELEASED);
|
|
});
|
|
this._transitionTo(_STATES.ACQUIRING);
|
|
}
|
|
|
|
|
|
_wakelockAcquired(wakelock) {
|
|
if (this._state === _STATES.ACQUIRING_WANT_RELEASE) {
|
|
// We were requested to release the wakelock while we were trying to
|
|
// acquire it. Now that we have acquired it, immediately release it.
|
|
wakelock.release();
|
|
this._transitionTo(_STATES.RELEASED);
|
|
return;
|
|
}
|
|
this._wakelock = wakelock;
|
|
this._wakelock.addEventListener("release", this._eventHandlers.wakelockReleased);
|
|
this._transitionTo(_STATES.ACQUIRED);
|
|
}
|
|
|
|
_wakelockReleased(event) {
|
|
this._wakelock = null;
|
|
if (document.visibilityState === "visible") {
|
|
Log.Warn("Lost wakelock, but document is still visible. Not reacquiring");
|
|
this._transitionTo(_STATES.RELEASED);
|
|
return;
|
|
}
|
|
this._awaitVisible();
|
|
}
|
|
|
|
_documentVisibilityChange(event) {
|
|
if (document.visibilityState !== "visible") {
|
|
return;
|
|
}
|
|
document.removeEventListener("visibilitychange", this._eventHandlers.documentVisibilityChange);
|
|
this._acquireWakelockNow();
|
|
}
|
|
}
|