Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
(() => {
if (navigator.keyboard) {
return;
}
let locked = false;
let pendingFullscreen = null;
let fakeFullscreenElement = null;
const origRequestFullscreen = Element.prototype.requestFullscreen;
const fullscreenDesc = Object.getOwnPropertyDescriptor(
Document.prototype,
"fullscreen"
);
const fullscreenElementDesc = Object.getOwnPropertyDescriptor(
Document.prototype,
"fullscreenElement"
);
Object.defineProperty(Document.prototype, "fullscreen", {
configurable: true,
enumerable: fullscreenDesc.enumerable,
get() {
return !!fakeFullscreenElement || fullscreenDesc.get.call(this);
},
});
Object.defineProperty(Document.prototype, "fullscreenElement", {
configurable: true,
enumerable: fullscreenElementDesc.enumerable,
get() {
return fakeFullscreenElement ?? fullscreenElementDesc.get.call(this);
},
});
function animationFrame() {
return new Promise(resolve => requestAnimationFrame(resolve));
}
function dispatchFakeFullscreenChange(element) {
element.dispatchEvent(
new Event("fullscreenchange", {
bubbles: true,
composed: true,
})
);
}
function clearPendingFullscreen() {
if (pendingFullscreen?.abortController) {
pendingFullscreen.abortController.abort();
}
pendingFullscreen = null;
fakeFullscreenElement = null;
}
Element.prototype.requestFullscreen = function (options = {}) {
if ("keyboardLock" in options) {
return origRequestFullscreen.call(this, options);
}
if (locked) {
return origRequestFullscreen.call(this, {
...options,
keyboardLock: "browser",
});
}
const element = this;
const abortController = new AbortController();
clearPendingFullscreen();
pendingFullscreen = {
element,
options,
abortController,
};
fakeFullscreenElement = element;
(async () => {
await animationFrame();
if (
abortController.signal.aborted ||
pendingFullscreen?.element !== element
) {
return;
}
dispatchFakeFullscreenChange(element);
await animationFrame();
if (
abortController.signal.aborted ||
pendingFullscreen?.element !== element
) {
return;
}
clearPendingFullscreen();
origRequestFullscreen.call(element, {
...options,
keyboardLock: "none",
});
})();
return Promise.resolve();
};
navigator.keyboard = {
async lock() {
locked = true;
if (pendingFullscreen) {
const { element, options } = pendingFullscreen;
clearPendingFullscreen();
await origRequestFullscreen.call(element, {
...options,
keyboardLock: "browser",
});
return undefined;
}
if (document.fullscreenElement && navigator.userActivation.isActive) {
await origRequestFullscreen.call(document.fullscreenElement, {
keyboardLock: "browser",
});
return undefined;
}
return Promise.resolve();
},
unlock() {
const lockStateChanged = locked === true;
locked = false;
if (
lockStateChanged &&
document.fullscreenElement &&
navigator.userActivation.isActive
) {
origRequestFullscreen.call(document.fullscreenElement, {
keyboardLock: "none",
});
}
},
};
const origPermissionsQuery = Permissions.prototype.query;
Permissions.prototype.query = function (permissionDesc) {
if (permissionDesc.name === "keyboard-lock") {
return Promise.resolve({ state: "granted" });
}
return origPermissionsQuery.call(this, permissionDesc);
};
})();