Skip to main content

Mountain/Vine/Server/Notification/
WebviewLifecycle.rs

1#![allow(non_snake_case)]
2//! Cocoon → Mountain `webview.setTitle` / `webview.setIconPath` /
3//! `webview.setHtml` / `webview.postMessage` / `webview.updateView` /
4//! `webview.viewState` / `webview.dispose` notifications. Shared atom
5//! because the methods all map to the same suffix-split pattern; keeping
6//! them in one file avoids near-identical 5-line files while still
7//! pinning the handler to a discoverable filename.
8//!
9//! Wire-shape canonicalisation MIRRORS `Track/Effect/CreateEffectForRequest/
10//! Webview.rs` so notification-path payloads land on the same named-key
11//! shapes as the request path. SkyBridge's listeners read `Payload.viewId`,
12//! `Payload.html`, `Payload.message` etc. directly; without this Cocoon's
13//! legacy positional `[Handle, Value]` notifications would emit a payload
14//! whose only top-level keys are `0`/`1` (array indices), the listener
15//! would early-return on the missing named keys, and the iframe would
16//! stay blank even when the request path canonicalised correctly.
17//!
18//! For per-extension isolation and payload inspection, split this into
19//! per-method atoms (`WebviewSetTitle`, `WebviewSetIconPath`, etc.) when
20//! the divergence is worth it.
21
22use serde_json::{Map, Value, json};
23use tauri::Emitter;
24
25use crate::{Vine::Server::MountainVinegRPCService::MountainVinegRPCService, dev_log};
26
27pub async fn WebviewLifecycle(Service:&MountainVinegRPCService, MethodName:&str, Parameter:&Value) {
28	// Suffix mapping: stock VS Code wire methods are camelCase
29	// (`webview.setHtml`, `webview.setIconPath`), but Sky's canonical
30	// channel registry (`Common/Source/IPC/SkyEvent.rs`) standardises
31	// kebab-case for set-html (`sky://webview/set-html`) since the
32	// `setWebviewHtml` typed-RPC and `Window.rs::SetHtml` request both
33	// emit kebab. Translate `setHtml` and `postMessage` here so every
34	// producer of those wire shapes lands on the same Sky channel; other
35	// suffixes pass through camelCase (Sky listeners use camel for
36	// `setTitle` / `setIconPath`).
37	let RawSuffix = &MethodName["webview.".len()..];
38
39	let Suffix = match RawSuffix {
40		"setHtml" => "set-html",
41
42		"postMessage" => "post-message",
43
44		Other => Other,
45	};
46
47	let EventName = format!("sky://webview/{}", Suffix);
48
49	// Canonicalise payload shapes to the same named-key form the request
50	// path produces. Three observed cases (matching `Webview.rs`):
51	//   1. Object: pass through (Cocoon's modern named-key
52	//      `SendToMountain("webview.setHtml", { handle, viewId, html })`).
53	//   2. Array `[<obj>]`: unwrap.
54	//   3. Array `[Handle, Second?, ...]`: positional - preserve the original args
55	//      slot AND project to the per-method named alias so listeners that read
56	//      `Payload.html` / `Payload.viewId` / `Payload.message` etc. stay
57	//      decoupled from the wire shape.
58	let CanonicalPayload:Value = if Parameter.is_object() {
59		Parameter.clone()
60	} else if let Some(First) = Parameter.get(0) {
61		if First.is_object() {
62			First.clone()
63		} else {
64			let mut Object = Map::new();
65
66			Object.insert("method".to_string(), Value::String(MethodName.to_string()));
67
68			Object.insert("handle".to_string(), First.clone());
69
70			Object.insert("args".to_string(), Parameter.clone());
71
72			if let Some(Second) = Parameter.get(1) {
73				let Alias = match MethodName {
74					"webview.setHtml" => "html",
75
76					"webview.postMessage" => "message",
77
78					"webview.registerView" | "webview.unregisterView" => "viewId",
79
80					"webview.registerCustomEditor" | "webview.unregisterCustomEditor" | "webview.create" => "viewType",
81
82					_ => "value",
83				};
84
85				Object.insert(Alias.to_string(), Second.clone());
86
87				if MethodName == "webview.create" {
88					if let Some(Third) = Parameter.get(2) {
89						Object.insert("title".to_string(), Third.clone());
90					}
91				}
92			}
93
94			Value::Object(Object)
95		}
96	} else {
97		json!({
98			"method": MethodName,
99			"handle": Parameter.clone(),
100		})
101	};
102
103	if let Err(Error) = Service.ApplicationHandle().emit(&EventName, &CanonicalPayload) {
104		dev_log!("grpc", "warn: [MountainVinegRPCService] {} emit failed: {}", EventName, Error);
105	}
106}