open method
Opens route in a window, or presents it the way this target can.
Never fails. Where a window cannot be created the route is navigated instead, and the reason is reported — a degradation nobody can see is the silent-ignoring the specification forbids.
Idempotent by route: opening a route a window already shows returns that
window rather than duplicating it, unless options.duplicate is set.
Implementation
Future<DVWindow> open(
DVRouteTarget route, {
DVWindowOptions options = const DVWindowOptions(),
}) async {
if (!options.duplicate) {
for (final existing in _windows) {
if (existing.route.path == route.path) return existing;
}
}
if (options.kind.isSpatial) return _openSpatial(route, options);
final int started = performance.mark();
final cap = capability;
DVWindowDegradation degradation = DVWindowDegradation.none;
String? nativeId;
// Resolved before the platform call, because the display id goes with it.
// Enumerate first when a hint was given and nothing has yet, or the first
// window of the process would always land on the primary display.
DVDisplayResolution? display;
if (options.display != null && cap.spatial != null) {
// Displays do not exist in space: the hint is ignored, and said so, as
// on any target with no matching display. Enumerating anyway would
// find the headset's own panel and "match" it.
display = const DVDisplayResolution(
display: null,
exact: false,
degradation: DVWindowDegradation.displayHintUnmatched,
);
} else if (options.display != null) {
if (_displays.value.isEmpty) await refreshDisplays();
display = DVDisplays.resolve(_displays.value, options.display);
}
final List<String> codes = <String>[];
// A kiosk-owned display is spoken for: another window asking for it is
// placed on a different one, and told (DV-WINDOW-011).
if (options.kind != DVWindowKind.kiosk && display?.display != null &&
_kioskOwners.containsKey(display!.display!.id)) {
final DVDisplay? elsewhere = _displays.value
.where((DVDisplay d) => !_kioskOwners.containsKey(d.id))
.fold<DVDisplay?>(null, (DVDisplay? best, DVDisplay d) => best == null || d.isPrimary ? d : best);
display = DVDisplayResolution(display: elsewhere, exact: false, degradation: DVWindowDegradation.none);
codes.add('DV-WINDOW-011');
}
// A kiosk window needs real windows, the capability to pin one to a
// display, and its display present; anything less and it presents in
// place, fullscreen, with its policy still running (DV-WINDOW-010).
final bool kioskInPlace = options.kind == DVWindowKind.kiosk &&
(!cap.multiWindow || !cap.displayKiosk || display?.display == null);
// An owned kind naming an owner that has already closed is refused rather
// than reparented. Adopting main would put a dialog on a window the user
// was not working in, and block input there.
final DVWindow? requestedOwner =
options.kind.isOwned ? options.owner : null;
final bool ownerGone = requestedOwner != null &&
!_windows.contains(requestedOwner);
// Application modality is honoured only where the OS can enforce it. A
// fake application-modal that leaks input is worse than an honest
// window-modal: the user finds the gap, and the thing meant to be blocked
// happens anyway.
DVWindowModality modality =
options.modality ?? options.kind.defaultModality;
final bool modalityReduced =
modality == DVWindowModality.application && !cap.applicationModal;
if (modalityReduced) modality = DVWindowModality.window;
if (kioskInPlace) {
degradation = DVWindowDegradation.displayUnavailable;
codes.add('DV-WINDOW-010');
} else if (ownerGone) {
degradation = DVWindowDegradation.ownerClosed;
} else if (options.kind.isOwned && !cap.ownedWindows) {
// Its own in-place fallback rather than a plain second window: a menu
// opening as a browser popup is worse than one drawn in the page.
degradation = DVWindowDegradation.capabilityUnsupported;
} else if (!cap.multiWindow) {
// Which of the three, because they are three different things to act
// on. Configuration first: `windowing.enabled: false` withdraws windows
// everywhere, kiosk or not, and it is the project's own line to change.
// Then a device-scope kiosk, which holds the surface for as long as it
// runs. Anything else is the target simply not having windows.
degradation = _declared.enabled == false
? DVWindowDegradation.disabledByConfig
: dvKioskLocksWindows
? DVWindowDegradation.kioskLocked
: DVWindowDegradation.capabilityUnsupported;
} else if (!DVNativeBridge.isRegistered('window.open')) {
// No binding is the ordinary state of one target: a browser has no
// `window.open` binding and cannot have one. What it has is a popup,
// which it opens inside a user gesture and refuses outside one. Asked
// here, so the browser's own refusal is reported as itself
// (DV-WINDOW-003) instead of as the integration defect below.
final DVBrowserWindowResult browser = browserWindowOpener(
route.path,
title: options.title,
width: options.size?.width,
height: options.size?.height,
);
switch (browser.outcome) {
case DVBrowserWindowOutcome.opened:
nativeId = browser.id;
case DVBrowserWindowOutcome.blocked:
degradation = DVWindowDegradation.gestureRequired;
case DVBrowserWindowOutcome.notWeb:
// The capability was claimed and nothing is there to honour it.
// That is an integration defect (DV-WINDOW-006, error), not the
// platform declining -- the bridge would answer null for a missing
// binding, and that read as an OS refusal.
degradation = DVWindowDegradation.bindingRefused;
}
} else {
try {
nativeId = await DVNativeBridge.invoke<String>(
'window.open',
<String, Object?>{
'route': route.path,
'kind': options.kind.name,
'title': options.title,
'width': options.size?.width,
'height': options.size?.height,
if (display?.display != null) 'displayId': display!.display!.id,
},
);
// Null is the binding's designed way of saying the platform said no:
// a window limit, a denied task. An exception is not designed; it is
// the binding breaking, and is reported as the bug it is.
if (nativeId == null) {
degradation = DVWindowDegradation.platformRefused;
}
} catch (_) {
degradation = DVWindowDegradation.bindingRefused;
}
}
// Only when nothing worse happened: a window that could not be created at
// all is the more useful report, and "it opened on the wrong screen" would
// be untrue as well as less severe.
if (degradation == DVWindowDegradation.none && display?.exact == false) {
degradation = display!.degradation;
}
// Quieter still than the display one: nothing was blocked that the
// platform could have blocked.
if (degradation == DVWindowDegradation.none && modalityReduced) {
degradation = DVWindowDegradation.modalityReduced;
}
final presentation = kioskInPlace
? DVWindowPresentation.page
: degradation == DVWindowDegradation.none ||
degradation == DVWindowDegradation.displayHintUnmatched ||
degradation == DVWindowDegradation.modalityReduced
? DVWindowPresentation.window
: _presentationFor(options.kind);
final window = DVWindow(
route: route,
kind: options.kind,
presentation: presentation,
degradation: degradation,
nativeId: nativeId,
owner: ownerGone ? null : requestedOwner,
modality: modality,
external: options.isExternal,
);
window.codes.addAll(codes);
for (final String code in codes) {
unawaited(window._logCode(code, code == 'DV-WINDOW-011'
? 'display ${options.display} is kiosk-owned; placed elsewhere'
: 'kiosk window presented in place, fullscreen'));
}
if (options.kind == DVWindowKind.kiosk) {
final DVWindowKiosk? spec = options.kiosk;
if (spec == null) {
throw ArgumentError('A kiosk window needs DVWindowOptions.kiosk with its declared policy.');
}
window.kiosk = DVWindowKioskHandle._(window, spec.policy, target: kioskTargetHere(), name: spec.name);
await window.kiosk!._runtime.resume();
if (!kioskInPlace && display?.display != null) {
_kioskOwners[display!.display!.id] = window;
}
}
_windows.add(window);
_all.value = List<DVWindow>.unmodifiable(_windows);
// The first principal window is main. An owned window opened first -- a
// dialog before anything else -- leaves main unset rather than becoming
// it, or a restored workspace would land inside a dialog.
if (_main.value == null && window.kind.countsAsPrincipal) {
_main.value = window;
}
// Opening a window undoes a previous "the last one closed".
if (_shouldExit.value) _shouldExit.value = false;
if (degradation != DVWindowDegradation.none) {
await _report(window, degradation, options.kind);
if (DV.Navigation.isAttached) DV.Navigation.navigate(route);
}
window.setLifecycle(DVWindowLifecycle.ready);
performance.recordOpenFrom(
started,
route: route.path,
virtual: window.isVirtual,
code: degradation == DVWindowDegradation.none ? null : degradation.code,
);
return window;
}