Merge pull request #1984 from tsukasa-au/add-wakelock-support
Add wakelock support
This commit is contained in:
commit
40cc57460d
21
app/ui.js
21
app/ui.js
|
|
@ -16,6 +16,7 @@ import KeyTable from "../core/input/keysym.js";
|
||||||
import keysyms from "../core/input/keysymdef.js";
|
import keysyms from "../core/input/keysymdef.js";
|
||||||
import Keyboard from "../core/input/keyboard.js";
|
import Keyboard from "../core/input/keyboard.js";
|
||||||
import RFB from "../core/rfb.js";
|
import RFB from "../core/rfb.js";
|
||||||
|
import WakeLockManager from './wakelock.js';
|
||||||
import * as WebUtil from "./webutil.js";
|
import * as WebUtil from "./webutil.js";
|
||||||
|
|
||||||
const PAGE_TITLE = "noVNC";
|
const PAGE_TITLE = "noVNC";
|
||||||
|
|
@ -46,6 +47,8 @@ const UI = {
|
||||||
reconnectCallback: null,
|
reconnectCallback: null,
|
||||||
reconnectPassword: null,
|
reconnectPassword: null,
|
||||||
|
|
||||||
|
wakeLockManager: new WakeLockManager(),
|
||||||
|
|
||||||
async start(options={}) {
|
async start(options={}) {
|
||||||
UI.customSettings = options.settings || {};
|
UI.customSettings = options.settings || {};
|
||||||
if (UI.customSettings.defaults === undefined) {
|
if (UI.customSettings.defaults === undefined) {
|
||||||
|
|
@ -189,6 +192,7 @@ const UI = {
|
||||||
UI.initSetting('repeaterID', '');
|
UI.initSetting('repeaterID', '');
|
||||||
UI.initSetting('reconnect', false);
|
UI.initSetting('reconnect', false);
|
||||||
UI.initSetting('reconnect_delay', 5000);
|
UI.initSetting('reconnect_delay', 5000);
|
||||||
|
UI.initSetting('keep_device_awake', false);
|
||||||
},
|
},
|
||||||
// Adds a link to the label elements on the corresponding input elements
|
// Adds a link to the label elements on the corresponding input elements
|
||||||
setupSettingLabels() {
|
setupSettingLabels() {
|
||||||
|
|
@ -371,6 +375,8 @@ const UI = {
|
||||||
UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
|
UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
|
||||||
UI.addSettingChangeHandler('show_dot');
|
UI.addSettingChangeHandler('show_dot');
|
||||||
UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
|
UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
|
||||||
|
UI.addSettingChangeHandler('keep_device_awake');
|
||||||
|
UI.addSettingChangeHandler('keep_device_awake', UI.updateRequestWakelock);
|
||||||
UI.addSettingChangeHandler('host');
|
UI.addSettingChangeHandler('host');
|
||||||
UI.addSettingChangeHandler('port');
|
UI.addSettingChangeHandler('port');
|
||||||
UI.addSettingChangeHandler('path');
|
UI.addSettingChangeHandler('path');
|
||||||
|
|
@ -1072,6 +1078,10 @@ const UI = {
|
||||||
url.protocol = (window.location.protocol === "https:") ? 'wss:' : 'ws:';
|
url.protocol = (window.location.protocol === "https:") ? 'wss:' : 'ws:';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UI.getSetting('keep_device_awake')) {
|
||||||
|
UI.wakeLockManager.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
UI.rfb = new RFB(document.getElementById('noVNC_container'),
|
UI.rfb = new RFB(document.getElementById('noVNC_container'),
|
||||||
url.href,
|
url.href,
|
||||||
|
|
@ -1171,6 +1181,7 @@ const UI = {
|
||||||
UI.connected = false;
|
UI.connected = false;
|
||||||
|
|
||||||
UI.rfb = undefined;
|
UI.rfb = undefined;
|
||||||
|
UI.wakeLockManager.release();
|
||||||
|
|
||||||
if (!e.detail.clean) {
|
if (!e.detail.clean) {
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
|
|
@ -1819,6 +1830,16 @@ const UI = {
|
||||||
document.title = e.detail.name + " - " + PAGE_TITLE;
|
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateRequestWakelock() {
|
||||||
|
if (!UI.rfb) return;
|
||||||
|
if (UI.getSetting('keep_device_awake')) {
|
||||||
|
UI.wakeLockManager.acquire();
|
||||||
|
} else {
|
||||||
|
UI.wakeLockManager.release();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
bell(e) {
|
bell(e) {
|
||||||
if (UI.getSetting('bell') === 'on') {
|
if (UI.getSetting('bell') === 'on') {
|
||||||
const promise = document.getElementById('noVNC_bell').play();
|
const promise = document.getElementById('noVNC_bell').play();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -92,6 +92,10 @@ Currently, the following options are available:
|
||||||
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
|
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
|
||||||
`debug`.
|
`debug`.
|
||||||
|
|
||||||
|
* `keep_device_awake` - Should we prevent the (local) display from going into
|
||||||
|
sleep mode while a connection is active? Useful for view-only sessions where
|
||||||
|
there unlikely to be any keyboard/mouse activity to keep the device active.
|
||||||
|
|
||||||
## HTTP serving considerations
|
## HTTP serving considerations
|
||||||
### Browser cache issue
|
### Browser cache issue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ module.exports = (config) => {
|
||||||
{ pattern: 'node_modules/sinon-chai/**', included: false },
|
{ pattern: 'node_modules/sinon-chai/**', included: false },
|
||||||
// modules to test
|
// modules to test
|
||||||
{ pattern: 'app/localization.js', included: false, type: 'module' },
|
{ pattern: 'app/localization.js', included: false, type: 'module' },
|
||||||
|
{ pattern: 'app/wakelock.js', included: false, type: 'module' },
|
||||||
{ pattern: 'app/webutil.js', included: false, type: 'module' },
|
{ pattern: 'app/webutil.js', included: false, type: 'module' },
|
||||||
{ pattern: 'core/**/*.js', included: false, type: 'module' },
|
{ pattern: 'core/**/*.js', included: false, type: 'module' },
|
||||||
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
|
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* jshint expr: true */
|
||||||
|
|
||||||
|
import WakeLockManager from '../app/wakelock.js';
|
||||||
|
|
||||||
|
class FakeWakeLockSentinal extends EventTarget {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.released = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async release() {
|
||||||
|
if (this.released) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.released = true;
|
||||||
|
this.dispatchEvent(new Event("release"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForStateTransition(wakelockManager, newState) {
|
||||||
|
const {promise, resolve} = Promise.withResolvers();
|
||||||
|
|
||||||
|
const eventListener = (event) => {
|
||||||
|
if (event.newState !== newState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wakelockManager.removeEventListener("testOnlyStateChange", eventListener);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
wakelockManager.addEventListener("testOnlyStateChange", eventListener);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WakeLockManager', function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let wakelockRequest;
|
||||||
|
beforeEach(function () {
|
||||||
|
wakelockRequest = sinon.stub(navigator.wakeLock, 'request');
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
wakelockRequest.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can acquire and release lock', async function () {
|
||||||
|
let wakeLockSentinal = new FakeWakeLockSentinal();
|
||||||
|
wakelockRequest.onFirstCall().resolves(wakeLockSentinal);
|
||||||
|
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
expect(wakelockRequest).to.not.have.been.called;
|
||||||
|
|
||||||
|
let done = waitForStateTransition(wlm, 'acquired');
|
||||||
|
wlm.acquire();
|
||||||
|
await done;
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
expect(wakeLockSentinal.released).to.be.false;
|
||||||
|
|
||||||
|
done = waitForStateTransition(wlm, 'released');
|
||||||
|
wlm.release();
|
||||||
|
await done;
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
expect(wakeLockSentinal.released).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can release without holding wakelock', async function () {
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
wlm.release();
|
||||||
|
expect(wakelockRequest).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can release while waiting for wakelock', async function () {
|
||||||
|
let wakeLockSentinal = new FakeWakeLockSentinal();
|
||||||
|
let {promise, resolve} = Promise.withResolvers();
|
||||||
|
|
||||||
|
wakelockRequest.onFirstCall().returns(promise);
|
||||||
|
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
expect(wakelockRequest).to.not.have.been.called;
|
||||||
|
|
||||||
|
let seenAcquiring = waitForStateTransition(wlm, 'acquiring');
|
||||||
|
let seenReleasing = waitForStateTransition(wlm, 'releasing');
|
||||||
|
let seenReleased = waitForStateTransition(wlm, 'released');
|
||||||
|
|
||||||
|
wlm.acquire();
|
||||||
|
await seenAcquiring;
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
|
||||||
|
// We can call acquire multiple times, while waiting for the promise
|
||||||
|
// to resolve.
|
||||||
|
wlm.acquire();
|
||||||
|
// It should not request a second wakelock.
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
|
||||||
|
wlm.release();
|
||||||
|
await seenReleasing;
|
||||||
|
|
||||||
|
expect(wakeLockSentinal.released).to.be.false;
|
||||||
|
|
||||||
|
// Now return the wake lock, we should immediately release it.
|
||||||
|
resolve(wakeLockSentinal);
|
||||||
|
await seenReleased;
|
||||||
|
expect(wakeLockSentinal.released).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles visibility loss', async function () {
|
||||||
|
let documentHidden = sinon.stub(document, 'hidden');
|
||||||
|
let documentVisibility = sinon.stub(document, 'visibilityState');
|
||||||
|
afterEach(function () {
|
||||||
|
documentHidden.restore();
|
||||||
|
documentVisibility.restore();
|
||||||
|
});
|
||||||
|
documentHidden.value(false);
|
||||||
|
documentVisibility.value('visible');
|
||||||
|
|
||||||
|
let wakeLockSentinal1 = new FakeWakeLockSentinal();
|
||||||
|
let wakeLockSentinal2 = new FakeWakeLockSentinal();
|
||||||
|
wakelockRequest.onFirstCall().resolves(wakeLockSentinal1);
|
||||||
|
wakelockRequest.onSecondCall().resolves(wakeLockSentinal2);
|
||||||
|
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
let seenAcquired = waitForStateTransition(wlm, 'acquired');
|
||||||
|
let seenAwaitingVisible = waitForStateTransition(wlm, 'awaiting_visible');
|
||||||
|
|
||||||
|
wlm.acquire();
|
||||||
|
await seenAcquired;
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
|
||||||
|
// Fake a visibility change.
|
||||||
|
documentHidden.value(true);
|
||||||
|
documentVisibility.value('hidden');
|
||||||
|
wakeLockSentinal1.release();
|
||||||
|
|
||||||
|
await seenAwaitingVisible;
|
||||||
|
seenAcquired = waitForStateTransition(wlm, 'acquired');
|
||||||
|
|
||||||
|
// Fake a visibility change back
|
||||||
|
documentHidden.value(false);
|
||||||
|
documentVisibility.value('visible');
|
||||||
|
document.dispatchEvent(new Event('visibilitychange'));
|
||||||
|
await seenAcquired;
|
||||||
|
|
||||||
|
expect(wakelockRequest).to.have.been.calledTwice;
|
||||||
|
expect(wakeLockSentinal2.released).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can start hidden', async function () {
|
||||||
|
let documentHidden = sinon.stub(document, 'hidden');
|
||||||
|
let documentVisibility = sinon.stub(document, 'visibilityState');
|
||||||
|
afterEach(function () {
|
||||||
|
documentHidden.restore();
|
||||||
|
documentVisibility.restore();
|
||||||
|
});
|
||||||
|
documentHidden.value(true);
|
||||||
|
documentVisibility.value('hidden');
|
||||||
|
|
||||||
|
let wakeLockSentinal = new FakeWakeLockSentinal();
|
||||||
|
wakelockRequest.onFirstCall().resolves(wakeLockSentinal);
|
||||||
|
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
let seenAwaitingVisible = waitForStateTransition(wlm, 'awaiting_visible');
|
||||||
|
let seenAcquired = waitForStateTransition(wlm, 'acquired');
|
||||||
|
|
||||||
|
wlm.acquire();
|
||||||
|
await seenAwaitingVisible;
|
||||||
|
expect(wakelockRequest).to.not.have.been.called;
|
||||||
|
|
||||||
|
// Fake a visibility change.
|
||||||
|
documentHidden.value(false);
|
||||||
|
documentVisibility.value('visible');
|
||||||
|
document.dispatchEvent(new Event('visibilitychange'));
|
||||||
|
await seenAcquired;
|
||||||
|
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
expect(wakeLockSentinal.released).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles acquire errors', async function () {
|
||||||
|
wakelockRequest.onFirstCall().rejects('WakeLockError');
|
||||||
|
let wakeLockSentinal = new FakeWakeLockSentinal();
|
||||||
|
wakelockRequest.onSecondCall().resolves(wakeLockSentinal);
|
||||||
|
|
||||||
|
let wlm = new WakeLockManager();
|
||||||
|
|
||||||
|
let seenError = waitForStateTransition(wlm, 'error');
|
||||||
|
wlm.acquire();
|
||||||
|
await seenError;
|
||||||
|
expect(wakelockRequest).to.have.been.calledOnce;
|
||||||
|
|
||||||
|
// Even though we saw an error previously, it will retry when
|
||||||
|
// requested.
|
||||||
|
let seenAcquired = waitForStateTransition(wlm, 'acquired');
|
||||||
|
wlm.acquire();
|
||||||
|
await seenAcquired;
|
||||||
|
expect(wakelockRequest).to.have.been.calledTwice;
|
||||||
|
});
|
||||||
|
});
|
||||||
7
vnc.html
7
vnc.html
|
|
@ -296,6 +296,13 @@
|
||||||
Show dot when no cursor
|
Show dot when no cursor
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input id="noVNC_setting_keep_device_awake" type="checkbox"
|
||||||
|
class="toggle">
|
||||||
|
Keep client display awake while connected
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<li><hr></li>
|
<li><hr></li>
|
||||||
<!-- Logging selection dropdown -->
|
<!-- Logging selection dropdown -->
|
||||||
<li>
|
<li>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue