Skip to content

子窗口系统

AuroraView 提供了统一的子窗口系统,允许示例和应用程序以独立模式或作为父应用程序(如 Gallery)的子窗口运行。

概述

子窗口系统支持:

  • 双模式执行:示例可以独立运行或作为子窗口运行
  • 自动模式检测:通过环境变量实现
  • 父子通信:窗口之间完整的 IPC 支持
  • 无缝集成:基本用法无需修改代码

架构

┌─────────────────────────────────────────────────────────────────┐
│                         Gallery (父窗口)                         │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐     ┌─────────────────┐                    │
│  │ ChildWindowManager │◄──►│   IPC Server    │                    │
│  └────────┬────────┘     └────────┬────────┘                    │
│           │                       │                              │
│           │  launch_example()     │  TCP Socket                  │
│           ▼                       ▼                              │
├───────────┴───────────────────────┴─────────────────────────────┤
│                         环境变量                                  │
│  AURORAVIEW_PARENT_ID, AURORAVIEW_PARENT_PORT 等                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │   示例 1    │  │   示例 2    │  │   示例 3    │              │
│  │  (子窗口)   │  │  (子窗口)   │  │  (子窗口)   │              │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘              │
│         │                │                │                      │
│         └────────────────┼────────────────┘                      │
│                          │                                       │
│                   ┌──────▼──────┐                                │
│                   │ ParentBridge │                                │
│                   │  (IPC 客户端)│                                │
│                   └─────────────┘                                │
└─────────────────────────────────────────────────────────────────┘

快速开始

使用 ChildContext 的基本用法

创建子窗口感知应用的最简单方式:

python
from auroraview import ChildContext

with ChildContext() as ctx:
    webview = ctx.create_webview(
        title="我的示例",
        html="<h1>Hello World</h1>",
        width=800,
        height=600
    )
    
    # 检查是否作为子窗口运行
    if ctx.is_child:
        print(f"作为子窗口运行,父窗口: {ctx.parent_id}")
        # 向父窗口发送消息
        ctx.emit_to_parent("hello", {"message": "来自子窗口的问候!"})
    else:
        print("独立运行")
    
    webview.show()

模式检测函数

python
from auroraview import is_child_mode, get_parent_id, get_child_id

# 检查是否作为子窗口运行
if is_child_mode():
    print(f"父窗口 ID: {get_parent_id()}")
    print(f"子窗口 ID: {get_child_id()}")
else:
    print("独立运行")

环境变量

作为子窗口启动时,会设置以下环境变量:

变量描述
AURORAVIEW_PARENT_ID父窗口标识符
AURORAVIEW_PARENT_PORTIPC 通信端口
AURORAVIEW_CHILD_ID唯一的子窗口 ID
AURORAVIEW_EXAMPLE_NAME正在运行的示例名称

API 参考

ChildContext

用于创建子窗口感知 WebView 的上下文管理器。

python
class ChildContext:
    def __init__(self):
        """初始化子窗口上下文,自动检测模式。"""
        
    @property
    def is_child(self) -> bool:
        """检查是否在子窗口模式下运行。"""
        
    @property
    def parent_id(self) -> Optional[str]:
        """获取父窗口 ID(仅在子窗口模式下)。"""
        
    @property
    def child_id(self) -> Optional[str]:
        """获取当前窗口的子窗口 ID(仅在子窗口模式下)。"""
        
    def create_webview(self, **kwargs) -> WebView:
        """根据当前模式创建适当配置的 WebView。"""
        
    def emit_to_parent(self, event: str, data: Any) -> bool:
        """向父窗口发送事件(仅在子窗口模式下有效)。"""
        
    def on_parent_message(self, handler: Callable[[str, Any], None]):
        """注册父窗口消息处理器。"""

ChildInfo

子窗口信息。

python
@dataclass
class ChildInfo:
    child_id: str          # 唯一子窗口标识符
    example_name: str      # 示例名称
    process_id: int        # 操作系统进程 ID
    port: int              # IPC 端口
    started_at: float      # 启动时间戳

辅助函数

python
def is_child_mode() -> bool:
    """检查是否作为子窗口运行。"""
    
def get_parent_id() -> Optional[str]:
    """获取父窗口 ID,独立运行时返回 None。"""
    
def get_child_id() -> Optional[str]:
    """获取当前窗口的子窗口 ID,独立运行时返回 None。"""
    
def run_example(example_path: str, **kwargs) -> Optional[str]:
    """将示例作为子窗口启动,返回 child_id。"""

父子通信

从子窗口到父窗口

python
# 在子窗口中
with ChildContext() as ctx:
    webview = ctx.create_webview(...)
    
    # 向父窗口发送事件
    ctx.emit_to_parent("status_update", {
        "progress": 50,
        "message": "处理中..."
    })

从父窗口到子窗口

python
# 在父窗口中(如 Gallery)
from gallery.backend.child_manager import get_manager

manager = get_manager()

# 向特定子窗口发送消息
manager.send_to_child(child_id, "parent:command", {
    "action": "refresh"
})

# 广播到所有子窗口
manager.broadcast("parent:notification", {
    "message": "设置已更改"
})

处理消息

python
# 在子窗口中
with ChildContext() as ctx:
    webview = ctx.create_webview(...)
    
    @ctx.on_parent_message
    def handle_parent_message(event: str, data: dict):
        if event == "parent:command":
            if data.get("action") == "refresh":
                # 处理刷新命令
                pass

JavaScript API

从 Gallery 运行示例时,使用以下 API:

javascript
// 将示例作为子窗口启动
const childId = await auroraview.api.launch_example_as_child("child_window_demo");

// 获取所有活动的子窗口
const children = await auroraview.api.get_children();
// 返回: [{ child_id, example_name, process_id, port, started_at }, ...]

// 向子窗口发送消息
await auroraview.api.send_to_child(childId, "parent:message", { data: "hello" });

// 广播到所有子窗口
await auroraview.api.broadcast_to_children("parent:notification", { message: "大家好" });

// 关闭特定子窗口
await auroraview.api.close_child(childId);

// 关闭所有子窗口
await auroraview.api.close_all_children();

监听子窗口事件

javascript
// 在 Gallery 前端
auroraview.on('child:connected', (data) => {
    console.log('子窗口已连接:', data.child_id, data.example_name);
});

auroraview.on('child:disconnected', (data) => {
    console.log('子窗口已断开:', data.child_id);
});

auroraview.on('child:message', (data) => {
    console.log('来自子窗口的消息:', data.child_id, data.event, data.data);
});

完整示例

以下是一个在独立模式和子窗口模式下都能工作的完整示例:

python
"""子窗口感知示例,根据执行上下文自动适应。"""
from auroraview import ChildContext

HTML = """
<!DOCTYPE html>
<html>
<head>
    <title>子窗口演示</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .mode { padding: 10px; border-radius: 5px; margin-bottom: 20px; }
        .standalone { background: #e3f2fd; }
        .child { background: #e8f5e9; }
        button { padding: 10px 20px; margin: 5px; cursor: pointer; }
    </style>
</head>
<body>
    <div id="mode" class="mode"></div>
    <div id="messages"></div>
    <button onclick="sendToParent()">发送到父窗口</button>
    
    <script>
        const isChild = window.AURORAVIEW_IS_CHILD || false;
        const modeDiv = document.getElementById('mode');
        
        if (isChild) {
            modeDiv.className = 'mode child';
            modeDiv.innerHTML = '<h2>作为子窗口运行</h2>';
        } else {
            modeDiv.className = 'mode standalone';
            modeDiv.innerHTML = '<h2>独立运行</h2>';
        }
        
        function sendToParent() {
            if (isChild && window.auroraview) {
                auroraview.api.notify_parent({
                    event: 'button_clicked',
                    data: { timestamp: Date.now() }
                });
            }
        }
        
        // 监听父窗口消息
        if (window.auroraview) {
            auroraview.on('parent:message', (data) => {
                const div = document.getElementById('messages');
                div.innerHTML += `<p>来自父窗口: ${JSON.stringify(data)}</p>`;
            });
        }
    </script>
</body>
</html>
"""

def main():
    with ChildContext() as ctx:
        webview = ctx.create_webview(
            title="子窗口演示",
            html=HTML,
            width=600,
            height=400
        )
        
        # 注入模式信息
        webview.eval_js(f"window.AURORAVIEW_IS_CHILD = {str(ctx.is_child).lower()};")
        
        # 处理来自父窗口的消息
        if ctx.is_child:
            @ctx.on_parent_message
            def on_parent_msg(event, data):
                webview.emit(event, data)
        
        # 子窗口通知父窗口的 API
        @webview.bind_call("api.notify_parent")
        def notify_parent(event: str, data: dict):
            if ctx.is_child:
                ctx.emit_to_parent(event, data)
                return {"ok": True}
            return {"ok": False, "reason": "不在子窗口模式"}
        
        webview.show()

if __name__ == "__main__":
    main()

最佳实践

1. 始终使用 ChildContext

python
# 推荐:使用上下文管理器
with ChildContext() as ctx:
    webview = ctx.create_webview(...)
    webview.show()

# 避免:手动模式检测
if os.environ.get("AURORAVIEW_PARENT_ID"):
    # 手动设置...

2. 优雅降级

设计应用在两种模式下都能工作:

python
with ChildContext() as ctx:
    webview = ctx.create_webview(...)
    
    # 仅在子窗口模式下可用的功能
    if ctx.is_child:
        ctx.emit_to_parent("ready", {"version": "1.0"})
    
    # 核心功能在两种模式下都能工作
    @webview.bind_call("api.process")
    def process(data):
        return do_processing(data)
    
    webview.show()

3. 清理关闭

python
with ChildContext() as ctx:
    webview = ctx.create_webview(...)
    
    @webview.on_close
    def on_close():
        if ctx.is_child:
            ctx.emit_to_parent("closing", {"child_id": ctx.child_id})
    
    webview.show()

与 Rust 子窗口的对比

特性Python 子窗口系统Rust child_window.rs
用途Python 示例作为子窗口JS window.open() 处理
通信完整 IPC
配置完整 WebView 选项仅 URL/尺寸
API 绑定支持不支持
模式检测自动不适用

这两个系统是互补的,而不是相互替代的。

Released under the MIT License.