tauri-runtime-cef 3.0.0-alpha.0

Tauri runtime interface for Chromium Embedded Framework
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use cef::ImplBrowserHost;
use objc2::rc::Retained;
use objc2_app_kit::{NSColor, NSView};
use objc2_foundation::{NSPoint, NSRect, NSSize};
use tauri_runtime::dpi::{LogicalPosition, LogicalSize, Rect};
use tauri_utils::config::Color;

use crate::{webview::AppWebview, window::AppWindow};

use super::utils;

impl AppWebview {
  fn try_nsview(&self) -> Option<Retained<NSView>> {
    let view = self.host.window_handle().cast::<NSView>();
    // CEF owns this NSView for the browser lifetime; retaining it protects the
    // synchronous UI-thread observation. A destroyed native view is unavailable.
    unsafe { Retained::<NSView>::retain(view) }
  }

  pub(crate) fn nsview(&self) -> Retained<NSView> {
    self.try_nsview().expect("failed to retain NSView")
  }

  pub(crate) fn native_parent_matches(&self, parent: &AppWindow) -> Option<bool> {
    let view = self.try_nsview()?;
    let superview = unsafe { view.superview() };
    Some(superview.as_deref() == Some(&*parent.nsview()))
  }

  pub(crate) fn native_visible(&self) -> Option<bool> {
    Some(!self.try_nsview()?.isHiddenOrHasHiddenAncestor())
  }

  pub(crate) fn set_background_color(&self, color: Option<Color>) {
    let nsview = self.nsview();

    nsview.setWantsLayer(true);

    let Some(layer) = nsview.layer() else {
      return;
    };

    let nscolor = color
      .map(utils::ns_color_from_tauri_color)
      .unwrap_or_else(NSColor::windowBackgroundColor);

    let cg_color = nscolor.CGColor();
    layer.setBackgroundColor(Some(&*cg_color));
  }

  pub(crate) fn bounds(&self) -> Option<Rect> {
    let nsview = self.try_nsview()?;

    let parent = unsafe { nsview.superview()? };
    let parent_frame = parent.frame();
    let frame = nsview.frame();

    let y = if parent.isFlipped() {
      frame.origin.y
    } else {
      parent_frame.size.height - frame.origin.y - frame.size.height
    };

    let position = LogicalPosition::new(frame.origin.x, y);
    let size = LogicalSize::new(frame.size.width, frame.size.height);

    Some(Rect {
      position: position.into(),
      size: size.into(),
    })
  }

  pub(crate) fn reparent(&self, parent: &AppWindow) {
    let view = self.nsview();
    let parent = parent.nsview();

    parent.addSubview(&view);
  }

  pub(crate) fn apply_visible(&self, visible: bool) {
    let nsview = self.nsview();

    nsview.setHidden(!visible);
  }

  /// Destroys CEF's own view for this browser, completing a close that
  /// `do_close` took over.
  ///
  /// The superview holds the only strong reference to that view, so dropping it
  /// deallocates the view — and its `dealloc` is what reports `WindowDestroyed`
  /// back to CEF.
  pub(crate) fn destroy_host_window(&self) {
    self.nsview().removeFromSuperview();
  }

  pub(crate) fn apply_physical_bounds(&self, scale: f64, x: i32, y: i32, width: i32, height: i32) {
    let nsview = self.nsview();
    let Some(parent) = (unsafe { nsview.superview() }) else {
      return;
    };

    // CEF provides child bounds as physical pixels, but NSView frames are logical pixels.
    let x = x as f64 / scale;
    let y = y as f64 / scale;
    let width = width as f64 / scale;
    let height = height as f64 / scale;

    let parent_frame = parent.frame();
    let y = if parent.isFlipped() {
      y
    } else {
      parent_frame.size.height - (y + height)
    };

    let frame = NSRect::new(NSPoint::new(x, y), NSSize::new(width, height));
    nsview.setFrame(frame);
  }
}