mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-03 10:11:15 +02:00
prepare multiwebview
This commit is contained in:
@@ -10,13 +10,13 @@ use std::{
|
||||
},
|
||||
};
|
||||
use tauri_runtime::{
|
||||
webview::{InitializationScript, UriSchemeProtocol},
|
||||
webview::{InitializationScript, PendingWebview, UriSchemeProtocol},
|
||||
window::{PendingWindow, WindowId},
|
||||
RunEvent, UserEvent,
|
||||
};
|
||||
use tauri_utils::html::normalize_script_for_csp;
|
||||
|
||||
use crate::{AppWindow, CefRuntime, Message};
|
||||
use crate::{AppWindow, BrowserViewWrapper, CefRuntime, Message};
|
||||
|
||||
mod request_handler;
|
||||
|
||||
@@ -135,9 +135,31 @@ wrap_client! {
|
||||
}
|
||||
}
|
||||
|
||||
wrap_browser_view_delegate! {
|
||||
struct BrowserViewDelegateImpl {
|
||||
use_alloy_style: bool,
|
||||
}
|
||||
|
||||
impl ViewDelegate {}
|
||||
|
||||
impl BrowserViewDelegate {
|
||||
fn browser_runtime_style(&self) -> RuntimeStyle {
|
||||
use cef::sys::cef_runtime_style_t;
|
||||
|
||||
if self.use_alloy_style {
|
||||
// Use Alloy style for additional webviews (multiwebview support)
|
||||
RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_ALLOY)
|
||||
} else {
|
||||
// Use Chrome style (default) for the first webview
|
||||
RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_CHROME)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_window_delegate! {
|
||||
struct AppWindowDelegate {
|
||||
browser_view: BrowserView,
|
||||
initial_browser_view: Option<BrowserView>,
|
||||
}
|
||||
|
||||
impl ViewDelegate {
|
||||
@@ -156,8 +178,11 @@ wrap_window_delegate! {
|
||||
impl WindowDelegate {
|
||||
fn on_window_created(&self, window: Option<&mut Window>) {
|
||||
if let Some(window) = window {
|
||||
let mut view = View::from(&self.browser_view);
|
||||
window.add_child_view(Some(&mut view));
|
||||
// If we have an initial browser view, add it
|
||||
if let Some(ref browser_view) = self.initial_browser_view {
|
||||
let mut view = View::from(browser_view);
|
||||
window.add_child_view(Some(&mut view));
|
||||
}
|
||||
window.show();
|
||||
}
|
||||
}
|
||||
@@ -188,6 +213,81 @@ wrap_window_delegate! {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_message<T: UserEvent>(context: &Context<T>, message: Message<T>) {
|
||||
match message {
|
||||
Message::CreateWindow {
|
||||
window_id,
|
||||
webview_id,
|
||||
pending,
|
||||
after_window_creation: _todo,
|
||||
} => create_window(context, window_id, webview_id, pending),
|
||||
Message::CreateWebview {
|
||||
window_id,
|
||||
webview_id,
|
||||
pending,
|
||||
} => create_webview(
|
||||
WebviewKind::WindowChild,
|
||||
context,
|
||||
window_id,
|
||||
webview_id,
|
||||
pending,
|
||||
),
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
Message::OpenDevTools {
|
||||
window_id,
|
||||
webview_id,
|
||||
} => {
|
||||
if let Some(app_window) = context.windows.borrow().get(&window_id) {
|
||||
if let Some(browser_view_wrapper) = app_window
|
||||
.webviews
|
||||
.iter()
|
||||
.find(|w| w.webview_id == webview_id)
|
||||
{
|
||||
if let Some(browser) = browser_view_wrapper.browser_view.browser() {
|
||||
if let Some(host) = browser.host() {
|
||||
// ShowDevTools(window_info, client, settings, inspect_element_at)
|
||||
// Using None for client and default settings, inspect at (0,0)
|
||||
let window_info = cef::WindowInfo::default();
|
||||
let settings = cef::BrowserSettings::default();
|
||||
let inspect_at = cef::Point { x: 0, y: 0 };
|
||||
host.show_dev_tools(
|
||||
Some(&window_info),
|
||||
Option::<&mut cef::Client>::None,
|
||||
Some(&settings),
|
||||
Some(&inspect_at),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
Message::CloseDevTools {
|
||||
window_id,
|
||||
webview_id,
|
||||
} => {
|
||||
if let Some(app_window) = context.windows.borrow().get(&window_id) {
|
||||
if let Some(browser_view_wrapper) = app_window
|
||||
.webviews
|
||||
.iter()
|
||||
.find(|w| w.webview_id == webview_id)
|
||||
{
|
||||
if let Some(browser) = browser_view_wrapper.browser_view.browser() {
|
||||
if let Some(host) = browser.host() {
|
||||
host.close_dev_tools();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Task(t) => t(),
|
||||
Message::UserEvent(evt) => {
|
||||
(context.callback.borrow_mut())(RunEvent::UserEvent(evt));
|
||||
}
|
||||
Message::Noop => {}
|
||||
}
|
||||
}
|
||||
|
||||
wrap_task! {
|
||||
pub struct SendMessageTask<T: UserEvent> {
|
||||
context: Context<T>,
|
||||
@@ -196,19 +296,7 @@ wrap_task! {
|
||||
|
||||
impl Task {
|
||||
fn execute(&self) {
|
||||
match self.message.replace(Message::Noop) {
|
||||
Message::CreateWindow {
|
||||
window_id,
|
||||
webview_id,
|
||||
pending,
|
||||
after_window_creation: _todo,
|
||||
} => create_window(&self.context, window_id, webview_id, pending),
|
||||
Message::Task(t) => t(),
|
||||
Message::UserEvent(evt) => {
|
||||
(self.context.callback.borrow_mut())(RunEvent::UserEvent(evt));
|
||||
}
|
||||
Message::Noop => {}
|
||||
}
|
||||
handle_message(&self.context, self.message.replace(Message::Noop));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,16 +304,70 @@ wrap_task! {
|
||||
fn create_window<T: UserEvent>(
|
||||
context: &Context<T>,
|
||||
window_id: WindowId,
|
||||
webview_id: u32,
|
||||
_webview_id: u32,
|
||||
pending: PendingWindow<T, CefRuntime<T>>,
|
||||
) {
|
||||
let label = pending.label.clone();
|
||||
|
||||
let webview = pending.webview.unwrap();
|
||||
// Create window delegate - we'll handle webviews separately
|
||||
// For windows without webviews, we use a delegate without initial browser view
|
||||
let mut delegate = AppWindowDelegate::new(None);
|
||||
|
||||
let window = window_create_top_level(Some(&mut delegate)).expect("Failed to create window");
|
||||
window.show();
|
||||
|
||||
// Insert window with empty webviews list
|
||||
context.windows.borrow_mut().insert(
|
||||
window_id,
|
||||
AppWindow {
|
||||
label,
|
||||
window,
|
||||
webviews: Vec::new(),
|
||||
content_panel: None,
|
||||
},
|
||||
);
|
||||
|
||||
// If a webview was provided, create it now
|
||||
if let Some(webview) = pending.webview {
|
||||
let webview_id = context.next_webview_id();
|
||||
create_webview(
|
||||
WebviewKind::WindowContent,
|
||||
context,
|
||||
window_id,
|
||||
webview_id,
|
||||
webview,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
enum WebviewKind {
|
||||
// webview is the entire window content
|
||||
WindowContent,
|
||||
// webview is a child of the window, which can contain other webviews too
|
||||
WindowChild,
|
||||
}
|
||||
|
||||
fn create_webview<T: UserEvent>(
|
||||
kind: WebviewKind,
|
||||
context: &Context<T>,
|
||||
window_id: WindowId,
|
||||
webview_id: u32,
|
||||
pending: PendingWebview<T, CefRuntime<T>>,
|
||||
) {
|
||||
// Get the window - return early if not found
|
||||
let mut windows = context.windows.borrow_mut();
|
||||
let app_window = match windows.get_mut(&window_id) {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
eprintln!("Window {:?} not found when creating webview", window_id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Get initialization scripts from webview attributes
|
||||
// Pre-compute script hashes once at webview creation time
|
||||
let initialization_scripts: Vec<_> = webview
|
||||
let initialization_scripts: Vec<_> = pending
|
||||
.webview_attributes
|
||||
.initialization_scripts
|
||||
.into_iter()
|
||||
@@ -233,7 +375,7 @@ fn create_window<T: UserEvent>(
|
||||
.collect();
|
||||
|
||||
let mut client = BrowserClient::new(initialization_scripts.clone());
|
||||
let url = CefString::from(webview.url.as_str());
|
||||
let url = CefString::from(pending.url.as_str());
|
||||
|
||||
let global_context =
|
||||
request_context_get_global_context().expect("Failed to get global request context");
|
||||
@@ -248,11 +390,8 @@ fn create_window<T: UserEvent>(
|
||||
);
|
||||
if let Some(request_context) = &request_context {
|
||||
// Ensure schemes are registered with proper flags (fetch-enabled, secure, etc.)
|
||||
// This is done in App::on_register_custom_schemes, but we also track
|
||||
// which schemes we've seen
|
||||
|
||||
for (scheme, handler) in webview.uri_scheme_protocols {
|
||||
let label = label.clone();
|
||||
for (scheme, handler) in pending.uri_scheme_protocols {
|
||||
let label = app_window.label.clone();
|
||||
request_context.register_scheme_handler_factory(
|
||||
Some(&scheme.as_str().into()),
|
||||
None,
|
||||
@@ -268,22 +407,72 @@ fn create_window<T: UserEvent>(
|
||||
}
|
||||
}
|
||||
|
||||
let mut browser_view_delegate =
|
||||
BrowserViewDelegateImpl::new(matches!(kind, WebviewKind::WindowChild));
|
||||
|
||||
let browser_view = browser_view_create(
|
||||
Some(&mut client),
|
||||
Some(&url),
|
||||
Some(&Default::default()),
|
||||
Option::<&mut DictionaryValue>::None,
|
||||
request_context.as_mut(),
|
||||
Option::<&mut BrowserViewDelegate>::None,
|
||||
Some(&mut browser_view_delegate),
|
||||
)
|
||||
.expect("Failed to create browser view");
|
||||
|
||||
let mut delegate = AppWindowDelegate::new(browser_view);
|
||||
let mut view = View::from(&browser_view);
|
||||
|
||||
let window = window_create_top_level(Some(&mut delegate)).expect("Failed to create window");
|
||||
let bounds = pending.webview_attributes.bounds.map(|bounds| {
|
||||
let device_scale_factor = app_window
|
||||
.window
|
||||
.display()
|
||||
.map(|d| d.device_scale_factor() as f64)
|
||||
.unwrap_or(1.0);
|
||||
let physical_position = bounds.position.to_physical::<i32>(device_scale_factor);
|
||||
let physical_size = bounds.size.to_physical::<u32>(device_scale_factor);
|
||||
Rect {
|
||||
x: physical_position.x,
|
||||
y: physical_position.y,
|
||||
width: physical_size.width as i32,
|
||||
height: physical_size.height as i32,
|
||||
}
|
||||
});
|
||||
|
||||
context
|
||||
.windows
|
||||
.borrow_mut()
|
||||
.insert(window_id, AppWindow { label, window });
|
||||
if let Some(bounds) = &bounds {
|
||||
view.set_bounds(Some(bounds));
|
||||
}
|
||||
|
||||
if kind == WebviewKind::WindowChild {
|
||||
let panel = if let Some(panel) = &app_window.content_panel {
|
||||
panel.clone()
|
||||
} else {
|
||||
let panel = cef::panel_create(None).expect("Failed to create content panel");
|
||||
|
||||
panel.set_bounds(Some(&app_window.window.bounds()));
|
||||
|
||||
use cef::BoxLayoutSettings;
|
||||
let mut layout_settings = BoxLayoutSettings::default();
|
||||
layout_settings.horizontal = 1;
|
||||
layout_settings.default_flex = 0;
|
||||
layout_settings.between_child_spacing = 0;
|
||||
panel.set_to_box_layout(Some(&layout_settings));
|
||||
|
||||
app_window
|
||||
.window
|
||||
.add_child_view(Some(&mut View::from(&panel)));
|
||||
|
||||
app_window.content_panel.replace(panel.clone());
|
||||
|
||||
panel
|
||||
};
|
||||
|
||||
panel.add_child_view(Some(&mut view));
|
||||
} else {
|
||||
app_window.window.add_child_view(Some(&mut view));
|
||||
}
|
||||
|
||||
app_window.webviews.push(BrowserViewWrapper {
|
||||
webview_id,
|
||||
browser_view,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ use std::{
|
||||
|
||||
use cef::{rc::*, *};
|
||||
use html5ever::{interface::QualName, namespace_url, ns, LocalName};
|
||||
use http::{header::CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::{
|
||||
header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE},
|
||||
HeaderMap, HeaderName, HeaderValue,
|
||||
};
|
||||
use kuchiki::NodeRef;
|
||||
use tauri_runtime::webview::{InitializationScript, UriSchemeProtocol};
|
||||
use tauri_runtime::webview::UriSchemeProtocol;
|
||||
use tauri_utils::{
|
||||
config::{Csp, CspDirectiveSources},
|
||||
html::{parse as parse_html, serialize_node},
|
||||
@@ -18,15 +21,10 @@ use url::Url;
|
||||
|
||||
use super::CefInitScript;
|
||||
|
||||
// Note: We handle head element manipulation inline in the filter
|
||||
|
||||
// ResponseFilter that injects initialization scripts into HTML responses
|
||||
// For HTTP/HTTPS with CSP headers, we inject a modified CSP meta tag
|
||||
wrap_response_filter! {
|
||||
pub struct HtmlScriptInjectionFilter {
|
||||
initialization_scripts: Vec<InitializationScript>,
|
||||
script_hashes: Vec<String>, // Pre-computed script hashes
|
||||
csp_header: Option<String>, // Original CSP header from HTTP response (if any)
|
||||
initialization_scripts: Vec<CefInitScript>,
|
||||
processed_html: RefCell<Option<Vec<u8>>>,
|
||||
output_offset: RefCell<usize>,
|
||||
}
|
||||
@@ -65,65 +63,13 @@ wrap_response_filter! {
|
||||
head_node
|
||||
};
|
||||
|
||||
// If CSP header exists, inject/modify CSP meta tag with script hashes
|
||||
// This ensures injected scripts work even when HTTP response has CSP header
|
||||
if let Some(ref original_csp) = self.csp_header {
|
||||
// Parse CSP using tauri-utils
|
||||
let mut csp_map: std::collections::HashMap<String, CspDirectiveSources> =
|
||||
Csp::Policy(original_csp.clone()).into();
|
||||
|
||||
// Update or create script-src directive with script hashes
|
||||
let script_src = csp_map
|
||||
.entry("script-src".to_string())
|
||||
.or_insert_with(|| CspDirectiveSources::List(vec!["'self'".to_string()]));
|
||||
|
||||
// Extend with script hashes
|
||||
script_src.extend(self.script_hashes.clone());
|
||||
|
||||
// Convert back to CSP string
|
||||
let updated_csp = Csp::DirectiveMap(csp_map).to_string();
|
||||
// Check if CSP meta tag already exists
|
||||
let should_update_meta = if let Ok(ref meta_node) = document.select_first("meta[http-equiv='Content-Security-Policy']") {
|
||||
let element = meta_node.as_node().as_element().unwrap();
|
||||
let mut attrs = element.attributes.borrow_mut();
|
||||
attrs.insert("content", updated_csp.clone());
|
||||
false // Updated existing meta tag, don't create new one
|
||||
} else {
|
||||
true // Need to create new meta tag
|
||||
};
|
||||
|
||||
if should_update_meta {
|
||||
use kuchiki::{Attribute, ExpandedName};
|
||||
let csp_meta = NodeRef::new_element(
|
||||
QualName::new(None, ns!(html), LocalName::from("meta")),
|
||||
vec![
|
||||
(
|
||||
ExpandedName::new(ns!(), LocalName::from("http-equiv")),
|
||||
Attribute {
|
||||
prefix: None,
|
||||
value: "Content-Security-Policy".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
ExpandedName::new(ns!(), LocalName::from("content")),
|
||||
Attribute {
|
||||
prefix: None,
|
||||
value: updated_csp.into(),
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
head.prepend(csp_meta);
|
||||
}
|
||||
}
|
||||
|
||||
// iterate in reverse order since we are prepending each script to the head tag
|
||||
for init_script in self.initialization_scripts.iter().rev() {
|
||||
let script_el = NodeRef::new_element(
|
||||
QualName::new(None, ns!(html), "script".into()),
|
||||
None,
|
||||
);
|
||||
script_el.append(NodeRef::new_text(init_script.script.as_str()));
|
||||
script_el.append(NodeRef::new_text(init_script.script.script.as_str()));
|
||||
head.prepend(script_el);
|
||||
}
|
||||
|
||||
@@ -233,29 +179,16 @@ wrap_resource_request_handler! {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Extract CSP header if present
|
||||
let csp_header = {
|
||||
let csp_header_name = CefString::from("Content-Security-Policy");
|
||||
let existing_csp = response.header_by_name(Some(&csp_header_name));
|
||||
// check if csp_header is not null manually - I believe header_by_name should return Option instead
|
||||
let csp_header: Option<&cef_dll_sys::_cef_string_utf16_t> = (&existing_csp).into();
|
||||
if csp_header.is_some() {
|
||||
Some(CefString::from(&existing_csp).to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let is_main_frame = frame.is_main() == 1;
|
||||
|
||||
// Filter scripts based on frame type
|
||||
let scripts_to_inject: Vec<_> = if is_main_frame {
|
||||
self.initialization_scripts.iter().map(|s| s.script.clone()).collect()
|
||||
self.initialization_scripts.clone()
|
||||
} else {
|
||||
self.initialization_scripts
|
||||
.iter()
|
||||
.filter(|s| !s.script.for_main_frame_only)
|
||||
.map(|s| s.script.clone())
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
|
||||
@@ -277,8 +210,6 @@ wrap_resource_request_handler! {
|
||||
// Return a filter that will inject scripts and update CSP in HTML if header exists
|
||||
Some(HtmlScriptInjectionFilter::new(
|
||||
scripts_to_inject,
|
||||
script_hashes,
|
||||
csp_header,
|
||||
RefCell::new(None),
|
||||
RefCell::new(0),
|
||||
))
|
||||
@@ -408,7 +339,7 @@ wrap_resource_handler! {
|
||||
for (name, value) in response_data.headers() {
|
||||
let Ok(value) = value.to_str() else { continue; };
|
||||
|
||||
if name.as_str().eq_ignore_ascii_case("content-security-policy") {
|
||||
if name == CONTENT_SECURITY_POLICY {
|
||||
csp_header = Some(value.to_string());
|
||||
} else {
|
||||
response.set_header_by_name(Some(&name.as_str().into()), Some(&value.into()), 0);
|
||||
@@ -434,7 +365,7 @@ wrap_resource_handler! {
|
||||
.map(|s| s.hash.clone())
|
||||
.collect();
|
||||
|
||||
let csp_header_name = CefString::from("Content-Security-Policy");
|
||||
let csp_header_name = CefString::from(CONTENT_SECURITY_POLICY.as_str());
|
||||
let new_csp = if let Some(existing_csp) = csp_header {
|
||||
// Parse CSP using tauri-utils
|
||||
let mut csp_map: std::collections::HashMap<String, CspDirectiveSources> =
|
||||
@@ -469,7 +400,7 @@ wrap_resource_handler! {
|
||||
} else if let Some(csp) = csp_header {
|
||||
// No scripts to inject, just copy the original CSP header
|
||||
response.set_header_by_name(
|
||||
Some(&CefString::from("Content-Security-Policy")),
|
||||
Some(&CefString::from(CONTENT_SECURITY_POLICY.as_str())),
|
||||
Some(&CefString::from(csp.as_str())),
|
||||
0,
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ use std::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread::{self, ThreadId},
|
||||
};
|
||||
|
||||
mod cef_impl;
|
||||
@@ -48,10 +49,41 @@ enum Message<T: UserEvent + 'static> {
|
||||
pending: PendingWindow<T, CefRuntime<T>>,
|
||||
after_window_creation: Option<Box<dyn Fn(RawWindow) + Send + 'static>>,
|
||||
},
|
||||
CreateWebview {
|
||||
window_id: WindowId,
|
||||
webview_id: u32,
|
||||
pending: PendingWebview<T, CefRuntime<T>>,
|
||||
},
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
OpenDevTools {
|
||||
window_id: WindowId,
|
||||
webview_id: u32,
|
||||
},
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
CloseDevTools {
|
||||
window_id: WindowId,
|
||||
webview_id: u32,
|
||||
},
|
||||
UserEvent(T),
|
||||
Noop,
|
||||
}
|
||||
|
||||
impl<T: UserEvent> fmt::Debug for Message<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Task(_) => write!(f, "Task"),
|
||||
Self::CreateWindow { .. } => write!(f, "CreateWindow"),
|
||||
Self::CreateWebview { .. } => write!(f, "CreateWebview"),
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
Self::OpenDevTools { .. } => write!(f, "OpenDevTools"),
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
Self::CloseDevTools { .. } => write!(f, "CloseDevTools"),
|
||||
Self::UserEvent(_) => write!(f, "UserEvent"),
|
||||
Self::Noop => write!(f, "Noop"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent> Clone for Message<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
@@ -61,9 +93,16 @@ impl<T: UserEvent> Clone for Message<T> {
|
||||
}
|
||||
}
|
||||
|
||||
struct AppWindow {
|
||||
label: String,
|
||||
window: cef::Window,
|
||||
pub(crate) struct BrowserViewWrapper {
|
||||
pub webview_id: u32,
|
||||
pub browser_view: cef::BrowserView,
|
||||
}
|
||||
|
||||
pub(crate) struct AppWindow {
|
||||
pub label: String,
|
||||
pub window: cef::Window,
|
||||
pub webviews: Vec<BrowserViewWrapper>,
|
||||
pub content_panel: Option<cef::Panel>, // Panel container for multiwebview (similar to Electron's contentView)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -71,6 +110,7 @@ pub struct RuntimeContext<T: UserEvent> {
|
||||
is_running: Arc<AtomicBool>,
|
||||
windows: Arc<RefCell<HashMap<WindowId, AppWindow>>>,
|
||||
main_thread_task_runner: cef::TaskRunner,
|
||||
main_thread_id: ThreadId,
|
||||
cef_context: cef_impl::Context<T>,
|
||||
event_queue: Arc<RefCell<Vec<RunEvent<T>>>>,
|
||||
}
|
||||
@@ -85,13 +125,20 @@ unsafe impl<T: UserEvent> Sync for RuntimeContext<T> {}
|
||||
|
||||
impl<T: UserEvent> RuntimeContext<T> {
|
||||
fn post_message(&self, message: Message<T>) -> Result<()> {
|
||||
self
|
||||
.main_thread_task_runner
|
||||
.post_task(Some(&mut cef_impl::SendMessageTask::new(
|
||||
self.cef_context.clone(),
|
||||
Arc::new(RefCell::new(message)),
|
||||
)));
|
||||
Ok(())
|
||||
if thread::current().id() == self.main_thread_id {
|
||||
// Already on main thread, execute directly
|
||||
cef_impl::handle_message(&self.cef_context, message);
|
||||
Ok(())
|
||||
} else {
|
||||
// Post to main thread via TaskRunner
|
||||
self
|
||||
.main_thread_task_runner
|
||||
.post_task(Some(&mut cef_impl::SendMessageTask::new(
|
||||
self.cef_context.clone(),
|
||||
Arc::new(RefCell::new(message)),
|
||||
)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
@@ -148,6 +195,29 @@ impl<T: UserEvent> RuntimeContext<T> {
|
||||
webview: detached_webview,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_webview(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, CefRuntime<T>>,
|
||||
) -> Result<DetachedWebview<T, CefRuntime<T>>> {
|
||||
let label = pending.label.clone();
|
||||
let webview_id = self.cef_context.next_webview_id();
|
||||
|
||||
self.post_message(Message::CreateWebview {
|
||||
window_id,
|
||||
webview_id,
|
||||
pending,
|
||||
})?;
|
||||
|
||||
let dispatcher = CefWebviewDispatcher {
|
||||
window_id: Arc::new(Mutex::new(window_id)),
|
||||
webview_id,
|
||||
context: self.clone(),
|
||||
};
|
||||
|
||||
Ok(DetachedWebview { label, dispatcher })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent> fmt::Debug for RuntimeContext<T> {
|
||||
@@ -201,7 +271,7 @@ impl<T: UserEvent> RuntimeHandle<T> for CefRuntimeHandle<T> {
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>> {
|
||||
todo!()
|
||||
self.context.create_webview(window_id, pending)
|
||||
}
|
||||
|
||||
/// Run a task on the main thread.
|
||||
@@ -538,10 +608,24 @@ impl<T: UserEvent> WebviewDispatch<T> for CefWebviewDispatcher<T> {
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
fn open_devtools(&self) {}
|
||||
fn open_devtools(&self) {
|
||||
let window_id = *self.window_id.lock().unwrap();
|
||||
let webview_id = self.webview_id;
|
||||
let _ = self.context.post_message(Message::OpenDevTools {
|
||||
window_id,
|
||||
webview_id,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
fn close_devtools(&self) {}
|
||||
fn close_devtools(&self) {
|
||||
let window_id = *self.window_id.lock().unwrap();
|
||||
let webview_id = self.webview_id;
|
||||
let _ = self.context.post_message(Message::CloseDevTools {
|
||||
window_id,
|
||||
webview_id,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
fn is_devtools_open(&self) -> Result<bool> {
|
||||
@@ -822,7 +906,7 @@ impl<T: UserEvent> WindowDispatch<T> for CefWindowDispatcher<T> {
|
||||
&mut self,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>> {
|
||||
todo!()
|
||||
self.context.create_webview(self.window_id, pending)
|
||||
}
|
||||
|
||||
fn set_resizable(&self, resizable: bool) -> Result<()> {
|
||||
@@ -1118,10 +1202,12 @@ impl<T: UserEvent> CefRuntime<T> {
|
||||
1
|
||||
);
|
||||
|
||||
let main_thread_id = thread::current().id();
|
||||
let context = RuntimeContext {
|
||||
is_running: is_running.clone(),
|
||||
windows: Default::default(),
|
||||
main_thread_task_runner: cef::task_runner_get_for_current_thread().expect("null task runner"),
|
||||
main_thread_id,
|
||||
cef_context,
|
||||
event_queue,
|
||||
};
|
||||
|
||||
@@ -1449,6 +1449,15 @@ impl Default for Builder<crate::Wry> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make `Cef` the default `Runtime` for `Builder`
|
||||
#[cfg(feature = "cef")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "cef")))]
|
||||
impl Default for Builder<crate::Cef> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "wry", feature = "cef")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "wry", feature = "cef")))))]
|
||||
impl<R: Runtime> Default for Builder<R> {
|
||||
|
||||
Reference in New Issue
Block a user