open method

Future<DVWindow> open(
  1. DVRouteTarget route, {
  2. DVWindowOptions options = const DVWindowOptions(),
})

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;
}