Handle page permissions - fakeivchenko/lwjwae GitHub Wiki

A page that asks for the camera or the microphone does not get an engine prompt in lwjwae. The window decides, and by default it says no, the same way on every platform. In this guide, a video call page gets the camera only when Java grants it.

A request that Java denies, on Linux and on Windows 11

The page has two buttons, one for the camera and one for the camera and the microphone, a line that tells how the request ended, and a list of what Java decided. Here the window has a handler that denies everything, which is also what a window without a handler does: the page asks, the request fails with NotAllowedError, and no prompt of the engine shows, neither on Linux nor on Windows.

Say yes to the camera

public class PermissionsGuide {
  public static void main(String[] args) {
    Application.launch(
        WindowParameters.of("Camera", 560, 460),
        window -> {
          window.permissionHandler(PermissionsGuide::decide);
          window.loadResource("permissions/index.html");
        });
  }

  private static PermissionDecision decide(PermissionRequest request) {
    return request.kind() == PermissionKind.CAMERA
            && request.origin().equals("https://meet.example.com")
        ? PermissionDecision.GRANT
        : PermissionDecision.DENY;
  }
}

The camera granted: the preview shows the picture of a test camera

The second click asks again, and this time the handler answers GRANT, so getUserMedia resolves with a stream and the page shows it in a video element. The list keeps both decisions, one line for every request that reached the handler. The pictures above come from a test camera, since the machines that took them have none. The origin is the one of the page, app://local on Linux and http://app.localhost on Windows, in the form of scheme://host, without the path.

Without permissionHandler, or with null, every request is denied: getUserMedia fails on the page with NotAllowedError, as if the user had clicked Block.

try {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  document.querySelector("#preview").srcObject = stream;
} catch (error) {
  document.querySelector("#status").textContent = error.name;
}

What reaches the handler

Kind Windows macOS Linux (GTK 3 and 4)
CAMERA yes yes yes
MICROPHONE yes yes yes
GEOLOCATION yes no hook: the system decides always denied
NOTIFICATIONS yes no hook: the system decides always denied

A call that asks for the camera and the microphone together reaches the handler as two requests, and the page gets both only if the handler grants both. A capture of the screen is always denied.

A request for the camera and the microphone, where Java grants the camera and denies the microphone

Here the handler grants the camera and denies the microphone. It hears of both, one line each, and the page gets nothing: getUserMedia({ video: true, audio: true }) fails with NotAllowedError as a whole. The order of the two lines is the one of the engine, the microphone first on Windows.

Good to know

  • The handler runs on the UI thread, so it has to answer without waiting. To ask the user, decide from what you already know, such as a setting, or open a dialog first and grant on the next call.
  • A handler that throws denies the request, and the exception is reported.
  • On macOS a grant is only the first step: the system then asks the user for its own permission of the application, and the application needs NSCameraUsageDescription or NSMicrophoneUsageDescription in Info.plist.
  • The decision is not remembered between runs; the handler is asked every time.