Skip to main content

Mountain/Environment/WebviewProvider/
Lifecycle.rs

1//! # WebviewProvider - Lifecycle Operations
2//!
3//! Implementation of webview panel lifecycle methods for
4//! [`MountainEnvironment`]
5//!
6//! Handles creation, disposal, and visibility management of webview panels.
7
8use CommonLibrary::{
9	Error::CommonError::CommonError,
10	IPC::SkyEvent::SkyEvent,
11	Webview::DTO::WebviewContentOptionsDTO::WebviewContentOptionsDTO,
12};
13use serde_json::{Value, json};
14use tauri::{Emitter, Manager, WebviewWindowBuilder};
15use uuid::Uuid;
16
17use super::super::{MountainEnvironment::MountainEnvironment, Utility};
18use crate::{ApplicationState::DTO::WebviewStateDTO::WebviewStateDTO, dev_log};
19
20/// Lifecycle operations implementation for MountainEnvironment
21pub(super) async fn create_webview_panel_impl(
22	env:&MountainEnvironment,
23
24	extension_data_value:Value,
25
26	view_type:String,
27
28	title:String,
29
30	_show_options_value:Value,
31
32	panel_options_value:Value,
33
34	content_options_value:Value,
35) -> Result<String, CommonError> {
36	let handle = Uuid::new_v4().to_string();
37
38	dev_log!(
39		"extensions",
40		"[WebviewProvider] Creating WebviewPanel with handle: {}, viewType: {}",
41		handle,
42		view_type
43	);
44
45	// Parse content options to ensure security settings
46	let content_options:WebviewContentOptionsDTO =
47		serde_json::from_value(content_options_value.clone()).map_err(|error| {
48			CommonError::InvalidArgument { ArgumentName:"ContentOptions".into(), Reason:error.to_string() }
49		})?;
50
51	let state = WebviewStateDTO {
52		Handle:handle.clone(),
53
54		ViewType:view_type.clone(),
55
56		Title:title.clone(),
57
58		ContentOptions:content_options,
59
60		PanelOptions:panel_options_value.clone(),
61
62		SideCarIdentifier:"cocoon-main".to_string(),
63
64		ExtensionIdentifier:extension_data_value
65			.get("id")
66			.and_then(|v| v.as_str())
67			.unwrap_or_default()
68			.to_string(),
69
70		IsActive:true,
71
72		IsVisible:true,
73	};
74
75	// Store the initial state with lifecycle state
76	{
77		let mut webview_guard = env
78			.ApplicationState
79			.Feature
80			.Webviews
81			.ActiveWebviews
82			.lock()
83			.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
84
85		webview_guard.insert(handle.clone(), state);
86	}
87
88	// Create a new Tauri window for this webview with security settings.
89	// Use the localhost plugin URL (http://localhost:PORT/Mountain/WebviewHost.html)
90	// so the webview's scripts are served by the same localhost plugin as the
91	// main window. WebviewUrl::App("WebviewHost.html") routes through
92	// tauri::manager which has no assets when frontendDist is null.
93	let title_clone = title.clone();
94
95	let WebviewHostUrl = crate::IPC::WindServiceHandlers::Utilities::LocalhostUrl::Get::Fn()
96		.map(|Base| format!("{}/Mountain/WebviewHost", Base))
97		.unwrap_or_else(|| "http://localhost:15536/Mountain/WebviewHost".to_string());
98
99	let WebviewUrlParsed = WebviewHostUrl
100		.parse::<url::Url>()
101		.map(tauri::WebviewUrl::External)
102		.unwrap_or_else(|_| tauri::WebviewUrl::App("WebviewHost.html".into()));
103
104	let _webview_window = WebviewWindowBuilder::new(&env.ApplicationHandle, &handle, WebviewUrlParsed)
105		.title(title)
106		.initialization_script(&format!(
107			"window.__WEBVIEW_INITIAL_STATE__ = {};",
108			json!({
109				"Handle": handle,
110				"ViewType": view_type,
111				"Title": title_clone
112			})
113		))
114		.build()
115		.map_err(|error| {
116			dev_log!(
117				"extensions",
118				"error: [WebviewProvider] Failed to create Webview window: {}",
119				error
120			);
121
122			CommonError::UserInterfaceInteraction { Reason:error.to_string() }
123		})?;
124
125	// Setup message listener for this Webview
126	crate::Environment::WebviewProvider::Messaging::setup_webview_message_listener_impl(env, handle.clone()).await?;
127
128	// Notify frontend about Webview creation
129	env.ApplicationHandle
130		.emit::<Value>(
131			SkyEvent::WebviewCreated.AsStr(),
132			json!({ "Handle": handle.clone(), "ViewType": view_type.clone(), "Title": title_clone }),
133		)
134		.map_err(|error| {
135			CommonError::IPCError { Description:format!("Failed to emit Webview creation event: {}", error) }
136		})?;
137
138	Ok(handle)
139}
140
141/// Disposes a Webview panel and cleans up all associated resources.
142pub(super) async fn dispose_webview_panel_impl(env:&MountainEnvironment, handle:String) -> Result<(), CommonError> {
143	dev_log!("extensions", "[WebviewProvider] Disposing WebviewPanel: {}", handle);
144
145	// Remove message listener
146	let _ = crate::Environment::WebviewProvider::Messaging::remove_webview_message_listener_impl(env, &handle).await;
147
148	// Close the window
149	if let Some(webview_window) = env.ApplicationHandle.get_webview_window(&handle) {
150		if let Err(error) = webview_window.close() {
151			dev_log!(
152				"extensions",
153				"warn: [WebviewProvider] Failed to close Webview window: {}",
154				error
155			);
156		}
157	}
158
159	// Remove state
160	env.ApplicationState
161		.Feature
162		.Webviews
163		.ActiveWebviews
164		.lock()
165		.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
166		.remove(&handle);
167
168	// Notify frontend about Webview disposal
169	env.ApplicationHandle
170		.emit::<Value>(SkyEvent::WebviewDisposed.AsStr(), json!({ "Handle": handle }))
171		.map_err(|error| {
172			CommonError::IPCError { Description:format!("Failed to emit Webview disposal event: {}", error) }
173		})?;
174
175	Ok(())
176}
177
178/// Reveals (shows and focuses) a Webview panel.
179pub(super) async fn reveal_webview_panel_impl(
180	env:&MountainEnvironment,
181
182	handle:String,
183
184	_show_options_value:Value,
185) -> Result<(), CommonError> {
186	dev_log!("extensions", "[WebviewProvider] Revealing WebviewPanel: {}", handle);
187
188	if let Some(webview_window) = env.ApplicationHandle.get_webview_window(&handle) {
189		webview_window.show().map_err(|error| {
190			CommonError::UserInterfaceInteraction { Reason:format!("Failed to show Webview window: {}", error) }
191		})?;
192
193		webview_window.set_focus().map_err(|error| {
194			CommonError::UserInterfaceInteraction { Reason:format!("Failed to focus Webview window: {}", error) }
195		})?;
196
197		// Update visibility state
198		{
199			let mut webview_guard = env
200				.ApplicationState
201				.Feature
202				.Webviews
203				.ActiveWebviews
204				.lock()
205				.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
206
207			if let Some(state) = webview_guard.get_mut(&handle) {
208				state.IsVisible = true;
209			}
210		}
211
212		// Emit visibility event
213		env.ApplicationHandle
214			.emit::<Value>(SkyEvent::WebviewRevealed.AsStr(), json!({ "Handle": handle }))
215			.map_err(|error| {
216				CommonError::IPCError { Description:format!("Failed to emit Webview revealed event: {}", error) }
217			})?;
218	}
219
220	Ok(())
221}