Repository: zng-ui/zng (language: rust)

Issue:
Implement zng-view window extension API
Similar to `RendererExtension`, access to raw handle and window builder config.

Maybe modify RendererExtension to include window stuff?


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08e16bca8..5aa8270ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,13 @@ fn version_0_5() -> impl UiNode {
 }
 ```
 * **Breaking** Rename `VarCapabilities` to `VarCapability`.
+* **Breaking** Add window extension API in `zng-view`.
+    - Add `ViewExtension::window`.
+    - Add `OpenGlContext` and replace the `gl` fields with `context` in multiple extension API args.
+    - Rename `is_config_only` to `is_init_only`.
+    - Note that this is only a breaking change for direct dependents of `zng-view`.
+    - Rename `ViewRenderExtensionError` to `ViewExtensionError`.
+* Add window reference to args for `RendererExtension` when possible.
 * Fix `zng::view_process::default::run_same_process` not propagating app panics.
 * Add `WindowCloseRequestedArgs::headed/headless`.
 * **Breaking** Fix tab nav when a focus scope with `FocusScopeOnFocus::LastFocused` is a child of
@@ -59,6 +66,7 @@ fn version_0_5() -> impl UiNode {
 * Add better conversions for `CommandScope`. You can now scope on named widgets directly, `FOO_CMD.scoped("bar-wgt")`.
 * Add `ContextualizedVar::new_value`.
 * **Breaking** `SCROLL.scroll_*` methods now return contextual vars, not values.
+* Fix panic on window move in Wayland.
 
 # 0.4.0
 
diff --git a/crates/zng-app/src/view_process.rs b/crates/zng-app/src/view_process.rs
index 1b845fb02..9f2bbcffe 100644
--- a/crates/zng-app/src/view_process.rs
+++ b/crates/zng-app/src/view_process.rs
@@ -835,6 +835,69 @@ impl ViewWindow {
     pub fn close(self) {
         drop(self)
     }
+
+    /// Call a window extension with custom encoded payload.
+    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
+        self.0.call(|id, p| p.window_extension(id, extension_id, request))
+    }
+
+    /// Call a window extension with serialized payload.
+    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
+    where
+        I: serde::Serialize,
+        O: serde::de::DeserializeOwned,
+    {
+        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
+        Ok(r.deserialize())
+    }
+}
+
+/// View window or headless surface.
+#[derive(Clone, Debug)]
+pub enum ViewWindowOrHeadless {
+    /// Headed window view.
+    Window(ViewWindow),
+    /// Headless surface view.
+    Headless(ViewHeadless),
+}
+impl ViewWindowOrHeadless {
+    /// Reference the window or surface renderer.
+    pub fn renderer(&self) -> ViewRenderer {
+        match self {
+            ViewWindowOrHeadless::Window(w) => w.renderer(),
+            ViewWindowOrHeadless::Headless(h) => h.renderer(),
+        }
+    }
+
+    /// Call a window extension with custom encoded payload.
+    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
+        match self {
+            ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
+            ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
+        }
+    }
+
+    /// Call a window extension with serialized payload.
+    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
+    where
+        I: serde::Serialize,
+        O: serde::de::DeserializeOwned,
+    {
+        match self {
+            ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
+            ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
+        }
+    }
+}
+impl From<ViewWindow> for ViewWindowOrHeadless {
+    fn from(w: ViewWindow) -> Self {
+        ViewWindowOrHeadless::Window(w)
+    }
+}
+impl From<ViewHeadless> for ViewWindowOrHeadless {
+    fn from(w: ViewHeadless) -> Self {
+        ViewWindowOrHeadless::Headless(w)
+    }
 }
 
 #[derive(Debug)]
@@ -887,6 +950,21 @@ impl ViewHeadless {
     pub fn renderer(&self) -> ViewRenderer {
         ViewRenderer(Arc::downgrade(&self.0))
     }
+
+    /// Call a window extension with custom encoded payload.
+    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
+        self.0.call(|id, p| p.window_extension(id, extension_id, request))
+    }
+
+    /// Call a window extension with serialized payload.
+    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
+    where
+        I: serde::Serialize,
+        O: serde::de::DeserializeOwned,
+    {
+        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
+        Ok(r.deserialize())
+    }
 }
 
 /// Weak handle to a window or view.
diff --git a/crates/zng-ext-window/src/control.rs b/crates/zng-ext-window/src/control.rs
index d20f5e02d..9ad8cc8db 100644
--- a/crates/zng-ext-window/src/control.rs
+++ b/crates/zng-ext-window/src/control.rs
@@ -677,7 +677,7 @@ impl HeadedCtrl {
             if args.window_id == WINDOW.id() {
                 self.waiting_view = false;
 
-                WINDOWS.set_renderer(args.window_id, args.window.renderer());
+                WINDOWS.set_view(args.window_id, args.window.clone().into());
 
                 self.window = Some(args.window.clone());
                 self.cancel_ime_handle = super::cmd::CANCEL_IME_CMD.scoped(WINDOW.id()).subscribe(true);
@@ -1498,7 +1498,7 @@ impl HeadlessWithRendererCtrl {
             if args.window_id == WINDOW.id() {
                 self.waiting_view = false;
 
-                WINDOWS.set_renderer(args.window_id, args.surface.renderer());
+                WINDOWS.set_view(args.window_id, args.surface.clone().into());
 
                 self.surface = Some(args.surface.clone());
                 self.vars.0.render_mode.set(args.data.render_mode);
diff --git a/crates/zng-ext-window/src/service.rs b/crates/zng-ext-window/src/service.rs
index 044ca7a60..ca2b1ddf8 100644
--- a/crates/zng-ext-window/src/service.rs
+++ b/crates/zng-ext-window/src/service.rs
@@ -12,7 +12,7 @@ use zng_app::{
             RAW_COLOR_SCHEME_CHANGED_EVENT, RAW_IMAGE_LOADED_EVENT, RAW_IMAGE_LOAD_ERROR_EVENT, RAW_WINDOW_CLOSE_EVENT,
             RAW_WINDOW_CLOSE_REQUESTED_EVENT, RAW_WINDOW_FOCUS_EVENT, RAW_WINDOW_OPEN_EVENT,
         },
-        ViewImage, ViewRenderer, VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
+        ViewImage, ViewRenderer, ViewWindowOrHeadless, VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
     },
     widget::{
         info::{InteractionPath, WidgetInfo, WidgetInfoTree},
@@ -164,8 +164,8 @@ impl WindowsService {
         action: impl FnOnce(&ViewRenderer) -> std::result::Result<ViewImage, ViewProcessOffline>,
     ) -> ImageVar {
         if let Some(w) = self.windows_info.get(&window_id) {
-            if let Some(r) = &w.renderer {
-                match action(r) {
+            if let Some(r) = &w.view {
+                match action(&r.renderer()) {
                     Ok(img) => {
                         let img = Img::new(img);
                         let img = var(img);
@@ -671,6 +671,61 @@ impl WINDOWS {
         std::mem::take(&mut WINDOWS_SV.write().windows_info.get_mut(&id).unwrap().extensions)
     }
 
+    /// Call a view-process headed window extension with custom encoded payload.
+    ///
+    /// Note that unlike most service methods this calls happens immediately.
+    pub fn view_window_extension_raw(
+        &self,
+        id: WindowId,
+        extension_id: ApiExtensionId,
+        request: ApiExtensionPayload,
+    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
+        let sv = WINDOWS_SV.read();
+        match WINDOWS_SV.read().windows_info.get(&id) {
+            Some(i) => match &i.view {
+                Some(r) => match r {
+                    ViewWindowOrHeadless::Window(r) => {
+                        let r = r.clone();
+                        drop(sv);
+                        r.window_extension_raw(extension_id, request)
+                            .map_err(ViewExtensionError::ViewProcessOffline)
+                    }
+                    ViewWindowOrHeadless::Headless(_) => Err(ViewExtensionError::WindowNotHeaded(id)),
+                },
+                None => Err(ViewExtensionError::NotOpenInViewProcess(id)),
+            },
+            None => Err(ViewExtensionError::WindowNotFound(WindowNotFound(id))),
+        }
+    }
+
+    /// Call a headed window extension with serialized payload.
+    ///
+    /// Note that unlike most service methods this call happens immediately.
+    pub fn view_window_extension<I, O>(&self, id: WindowId, extension_id: ApiExtensionId, request: &I) -> Result<O, ViewExtensionError>
+    where
+        I: serde::Serialize,
+        O: serde::de::DeserializeOwned,
+    {
+        let sv = WINDOWS_SV.read();
+        match sv.windows_info.get(&id) {
+            Some(i) => match &i.view {
+                Some(r) => match r {
+                    ViewWindowOrHeadless::Window(r) => {
+                        let r = r.clone();
+                        drop(sv);
+                        let r = r
+                            .window_extension(extension_id, request)
+                            .map_err(ViewExtensionError::ViewProcessOffline)?;
+                        r.map_err(ViewExtensionError::Api)
+                    }
+                    ViewWindowOrHeadless::Headless(_) => Err(ViewExtensionError::WindowNotHeaded(id)),
+                },
+                None => Err(ViewExtensionError::NotOpenInViewProcess(id)),
+            },
+            None => Err(ViewExtensionError::WindowNotFound(WindowNotFound(id))),
+        }
+    }
+
     /// Call a view-process render extension with custom encoded payload for the renderer associated with the window.
     ///
     /// Note that unlike most service methods this call happens immediately.
@@ -679,56 +734,51 @@ impl WINDOWS {
         id: WindowId,
         extension_id: ApiExtensionId,
         request: ApiExtensionPayload,
-    ) -> Result<ApiExtensionPayload, ViewRenderExtensionError> {
+    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
         let sv = WINDOWS_SV.read();
         match WINDOWS_SV.read().windows_info.get(&id) {
-            Some(i) => match &i.renderer {
+            Some(i) => match &i.view {
                 Some(r) => {
-                    let r = r.clone();
+                    let r = r.renderer();
                     drop(sv);
                     r.render_extension_raw(extension_id, request)
-                        .map_err(ViewRenderExtensionError::ViewProcessOffline)
+                        .map_err(ViewExtensionError::ViewProcessOffline)
                 }
-                None => Err(ViewRenderExtensionError::NotOpenInViewProcess),
+                None => Err(ViewExtensionError::NotOpenInViewProcess(id)),
             },
-            None => Err(ViewRenderExtensionError::WindowNotFound(WindowNotFound(id))),
+            None => Err(ViewExtensionError::WindowNotFound(WindowNotFound(id))),
         }
     }
 
     /// Call a render extension with serialized payload for the renderer associated with the window.
     ///
     /// Note that unlike most service methods this call happens immediately.
-    pub fn view_render_extension<I, O>(
-        &self,
-        id: WindowId,
-        extension_id: ApiExtensionId,
-        request: &I,
-    ) -> Result<O, ViewRenderExtensionError>
+    pub fn view_render_extension<I, O>(&self, id: WindowId, extension_id: ApiExtensionId, request: &I) -> Result<O, ViewExtensionError>
     where
         I: serde::Serialize,
         O: serde::de::DeserializeOwned,
     {
         let sv = WINDOWS_SV.read();
         match sv.windows_info.get(&id) {
-            Some(i) => match &i.renderer {
+            Some(i) => match &i.view {
                 Some(r) => {
-                    let r = r.clone();
+                    let r = r.renderer();
                     drop(sv);
                     let r = r
                         .render_extension(extension_id, request)
-                        .map_err(ViewRenderExtensionError::ViewProcessOffline)?;
-                    r.map_err(ViewRenderExtensionError::Api)
+                        .map_err(ViewExtensionError::ViewProcessOffline)?;
+                    r.map_err(ViewExtensionError::Api)
                 }
-                None => Err(ViewRenderExtensionError::NotOpenInViewProcess),
+                None => Err(ViewExtensionError::NotOpenInViewProcess(id)),
             },
-            None => Err(ViewRenderExtensionError::WindowNotFound(WindowNotFound(id))),
+            None => Err(ViewExtensionError::WindowNotFound(WindowNotFound(id))),
         }
     }
 
-    /// Update the reference to the renderer associated with the window.
-    pub(super) fn set_renderer(&self, id: WindowId, renderer: ViewRenderer) {
+    /// Update the reference to view window the renderer associated with the window.
+    pub(super) fn set_view(&self, id: WindowId, view: ViewWindowOrHeadless) {
         if let Some(info) = WINDOWS_SV.write().windows_info.get_mut(&id) {
-            info.renderer = Some(renderer);
+            info.view = Some(view);
         }
     }
 
@@ -1266,7 +1316,7 @@ impl WINDOWS {
 struct AppWindowInfo {
     id: WindowId,
     mode: WindowMode,
-    renderer: Option<ViewRenderer>,
+    view: Option<ViewWindowOrHeadless>,
     extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
     vars: WindowVars,
 
@@ -1282,7 +1332,7 @@ impl AppWindowInfo {
         Self {
             id,
             mode,
-            renderer: None,
+            view: None,
             extensions: vec![],
             vars,
             widget_tree: WidgetInfoTree::wgt(id, root_id),
@@ -1723,38 +1773,42 @@ impl WINDOW_FOCUS {
     }
 }
 
-/// Error calling a view-process API extension associated with a window renderer.
+/// Error calling a view-process API extension associated with a window or renderer.
 #[derive(Debug)]
-pub enum ViewRenderExtensionError {
+pub enum ViewExtensionError {
     /// Window is not open in the `WINDOWS` service.
     WindowNotFound(WindowNotFound),
+    /// Window must be headed to call window extensions.
+    WindowNotHeaded(WindowId),
     /// Window is not open in the view-process.
     ///
     /// If the window is headless without renderer it will never open in view-process, if the window is headed
     /// headless with renderer the window opens in the view-process after the first layout.
-    NotOpenInViewProcess,
+    NotOpenInViewProcess(WindowId),
     /// View-process is not running.
     ViewProcessOffline(ViewProcessOffline),
     /// Api Error.
     Api(zng_view_api::api_extension::ApiExtensionRecvError),
 }
-impl fmt::Display for ViewRenderExtensionError {
+impl fmt::Display for ViewExtensionError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            ViewRenderExtensionError::WindowNotFound(e) => fmt::Display::fmt(e, f),
-            ViewRenderExtensionError::NotOpenInViewProcess => write!(f, "renderer not open in the view-process"),
-            ViewRenderExtensionError::ViewProcessOffline(e) => fmt::Display::fmt(e, f),
-            ViewRenderExtensionError::Api(e) => fmt::Display::fmt(e, f),
+            ViewExtensionError::WindowNotFound(e) => fmt::Display::fmt(e, f),
+            ViewExtensionError::WindowNotHeaded(id) => write!(f, "window `{id}` is not headed"),
+            ViewExtensionError::NotOpenInViewProcess(id) => write!(f, "window/renderer `{id}` not open in the view-process"),
+            ViewExtensionError::ViewProcessOffline(e) => fmt::Display::fmt(e, f),
+            ViewExtensionError::Api(e) => fmt::Display::fmt(e, f),
         }
     }
 }
-impl std::error::Error for ViewRenderExtensionError {
+impl std::error::Error for ViewExtensionError {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         match self {
-            ViewRenderExtensionError::WindowNotFound(e) => Some(e),
-            ViewRenderExtensionError::NotOpenInViewProcess => None,
-            ViewRenderExtensionError::ViewProcessOffline(e) => Some(e),
-            ViewRenderExtensionError::Api(e) => Some(e),
+            ViewExtensionError::WindowNotFound(e) => Some(e),
+            ViewExtensionError::WindowNotHeaded(_) => None,
+            ViewExtensionError::NotOpenInViewProcess(_) => None,
+            ViewExtensionError::ViewProcessOffline(e) => Some(e),
+            ViewExtensionError::Api(e) => Some(e),
         }
     }
 }
diff --git a/crates/zng-view-api/src/lib.rs b/crates/zng-view-api/src/lib.rs
index ab492eb79..00d9794b3 100644
--- a/crates/zng-view-api/src/lib.rs
+++ b/crates/zng-view-api/src/lib.rs
@@ -520,6 +520,12 @@ declare_api! {
     /// format expected by the extension.
     pub fn app_extension(&mut self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> ApiExtensionPayload;
 
+    /// Call the API extension.
+    ///
+    /// This is similar to [`Api::app_extension`], but is targeting the instance of an extension associated
+    /// with the `id` window or headless surface.
+    pub fn window_extension(&mut self, id: WindowId, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> ApiExtensionPayload;
+
     /// Call the API extension.
     ///
     /// This is similar to [`Api::app_extension`], but is targeting the instance of an extension associated
diff --git a/crates/zng-view-api/src/window.rs b/crates/zng-view-api/src/window.rs
index 1cc9b402b..83fa13b8c 100644
--- a/crates/zng-view-api/src/window.rs
+++ b/crates/zng-view-api/src/window.rs
@@ -117,7 +117,8 @@ pub struct HeadlessRequest {
 
     /// Initial payload for API extensions.
     ///
-    /// The `zng-view` crate implements this by calling `RendererExtension::configure` with the payload.
+    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure`
+    /// with the payload.
     pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
 }
 
@@ -406,7 +407,7 @@ pub struct WindowRequest {
 
     /// Initial payload for API extensions.
     ///
-    /// The `zng-view` crate implements this by calling `RendererExtension::configure` with the payload.
+    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure` with the payload.
     pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
 
     /// IME cursor area, if IME is enabled.
diff --git a/crates/zng-view/Cargo.toml b/crates/zng-view/Cargo.toml
index 0406ab963..ca6503646 100644
--- a/crates/zng-view/Cargo.toml
+++ b/crates/zng-view/Cargo.toml
@@ -61,7 +61,7 @@ winit = { version = "0.30", default-features = false, features = [
     "rwh_06",
 ] }
 glutin = "0.31"
-raw-window-handle = "0.5" # matches glutin
+raw-window-handle = "0.5.2" # matches glutin
 flume = "0.11"
 image = { version = "0.25", default-features = false, features = [
     "rayon",
diff --git a/crates/zng-view/src/extensions.rs b/crates/zng-view/src/extensions.rs
index aefa38eae..17abd73d7 100644
--- a/crates/zng-view/src/extensions.rs
+++ b/crates/zng-view/src/extensions.rs
@@ -5,6 +5,7 @@
 //! the view-process without needing to fork it or re-implement the entire view API from scratch.
 //!
 
+use std::rc::Rc;
 use std::{any::Any, sync::Arc};
 
 use webrender::api::{
@@ -13,6 +14,7 @@ use webrender::api::{
 };
 use webrender::{DebugFlags, RenderApi};
 use zng_unit::{Factor, PxSize};
+use zng_view_api::window::RenderMode;
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensions},
     Event,
@@ -21,6 +23,7 @@ use zng_view_api::{
 use crate::display_list::{DisplayExtensionArgs, DisplayExtensionItemArgs, DisplayExtensionUpdateArgs, DisplayListExtension, SpaceAndClip};
 
 pub use crate::px_wr::{PxToWr, WrToPx};
+use crate::util::PxToWinit;
 
 /// The extension API.
 pub trait ViewExtension: Send + Any {
@@ -38,6 +41,11 @@ pub trait ViewExtension: Send + Any {
         None
     }
 
+    /// Create a [`WindowExtension`] for a new window instance.
+    fn window(&mut self) -> Option<Box<dyn WindowExtension>> {
+        None
+    }
+
     /// Create a [`RendererExtension`] for a new renderer instance.
     fn renderer(&mut self) -> Option<Box<dyn RendererExtension>> {
         None
@@ -47,6 +55,41 @@ pub trait ViewExtension: Send + Any {
     fn low_memory(&mut self) {}
 }
 
+/// Represents a view extension associated with a headed window instance.
+pub trait WindowExtension: Any {
+    /// Edit attributes for the new window.
+    fn configure(&mut self, args: &mut WindowConfigArgs) {
+        let _ = args;
+    }
+
+    /// Called just after the window is created.
+    fn window_inited(&mut self, args: &mut WindowInitedArgs) {
+        let _ = args;
+    }
+
+    /// If this extension can be dropped after window creation.
+    fn is_init_only(&self) -> bool;
+
+    /// Called when a command request is made for the extension and window (window ID).
+    fn command(&mut self, args: &mut WindowCommandArgs) -> ApiExtensionPayload {
+        let _ = args;
+        ApiExtensionPayload::unknown_extension(ApiExtensionId::INVALID)
+    }
+
+    /// Called when the window receives an event.
+    fn event(&mut self, args: &mut WindowEventArgs) {
+        let _ = args;
+    }
+
+    /// System warning low memory, release unused memory, caches.
+    fn low_memory(&mut self) {}
+
+    /// Called just after the window closes.
+    fn window_deinited(&mut self, args: &mut WindowDeinitedArgs) {
+        let _ = args;
+    }
+}
+
 /// Represents a view extension associated with a renderer instance.
 pub trait RendererExtension: Any {
     /// Edit options for the new renderer.
@@ -59,17 +102,10 @@ pub trait RendererExtension: Any {
         let _ = args;
     }
 
-    /// Called just before the renderer is destroyed.
-    fn renderer_deinited(&mut self, args: &mut RendererDeinitedArgs) {
-        let _ = args;
-    }
-
     /// If this extension can be dropped after render creation.
-    fn is_config_only(&self) -> bool;
+    fn is_init_only(&self) -> bool;
 
     /// Called when a command request is made for the extension and renderer (window ID).
-    ///
-    /// The `extension_id` is the current index of the extension, it can be used in error messages.
     fn command(&mut self, args: &mut RendererCommandArgs) -> ApiExtensionPayload {
         let _ = args;
         ApiExtensionPayload::unknown_extension(ApiExtensionId::INVALID)
@@ -114,6 +150,11 @@ pub trait RendererExtension: Any {
 
     /// System warning low memory, release unused memory, caches.
     fn low_memory(&mut self) {}
+
+    /// Called just before the renderer is destroyed.
+    fn renderer_deinited(&mut self, args: &mut RendererDeinitedArgs) {
+        let _ = args;
+    }
 }
 
 /// Arguments for [`RendererExtension::render_start`] and [`RendererExtension::render_end`].
@@ -217,7 +258,7 @@ pub struct RedrawArgs<'a> {
     /// OpenGL context used by the renderer.
     ///
     /// The context is current, and Webrender has already redraw.
-    pub gl: &'a dyn gleam::gl::Gl,
+    pub context: &'a mut dyn OpenGlContext,
 }
 
 /// Represents a Webrender blob handler that can coexist with other blob handlers on the same renderer.
@@ -355,12 +396,23 @@ pub struct BlobRasterizerArgs<'a> {
     pub responses: &'a mut Vec<(BlobImageRequest, BlobImageResult)>,
 }
 
+/// Arguments for [`WindowExtension::configure`]
+pub struct WindowConfigArgs<'a> {
+    /// Config payload send with the window creation request addressed to this extension.
+    ///
+    /// Note that this extension will participate in the renderer creation even if there is no config for it.
+    pub config: Option<&'a ApiExtensionPayload>,
+
+    /// Window attributes that will be used to build the headed window.
+    pub window: &'a mut winit::window::WindowAttributes,
+}
+
 /// Arguments for [`RendererExtension::configure`]
 pub struct RendererConfigArgs<'a> {
     /// Config payload send with the renderer creation request addressed to this extension.
     ///
     /// Note that this extension will participate in the renderer creation even if there is no config for it.
-    pub config: Option<ApiExtensionPayload>,
+    pub config: Option<&'a ApiExtensionPayload>,
 
     /// Webrender options.
     ///
@@ -376,6 +428,12 @@ pub struct RendererConfigArgs<'a> {
     ///
     /// Use this API instead of `blob_image_handler` in options to support multiple blob handlers.
     pub blobs: &'a mut Vec<Box<dyn BlobExtension>>,
+
+    /// Winit window if the renderer is associated with a headed window.
+    pub window: Option<&'a winit::window::Window>,
+
+    /// OpenGL context that will be used by the new renderer.
+    pub context: &'a mut dyn OpenGlContext,
 }
 
 /// Arguments for [`RendererExtension::renderer_inited`].
@@ -400,10 +458,13 @@ pub struct RendererInitedArgs<'a> {
     /// The pipeline of the main content.
     pub pipeline_id: PipelineId,
 
+    /// Winit window if the renderer is associated with a headed window.
+    pub window: Option<&'a winit::window::Window>,
+
     /// OpenGL context used by the new renderer.
     ///
     /// The context is new and current, only Webrender and previous extensions have interacted with it.
-    pub gl: &'a dyn gleam::gl::Gl,
+    pub context: &'a mut dyn OpenGlContext,
 
     /// External images registry for the `renderer`.
     pub external_images: &'a mut ExternalImages,
@@ -470,11 +531,109 @@ pub struct RendererDeinitedArgs<'a> {
     /// The pipeline of the main content, already deinited.
     pub pipeline_id: PipelineId,
 
+    /// Winit window if the renderer is associated with a headed window.
+    pub window: Option<&'a winit::window::Window>,
+
     /// OpenGL context.
     ///
     /// The context is current and Webrender has already deinited, the context will be dropped
     /// after all extensions handle deinit.
-    pub gl: &'a dyn gleam::gl::Gl,
+    pub context: &'a mut dyn OpenGlContext,
+}
+
+/// Arguments for [`WindowExtension::window_inited`].
+pub struct WindowInitedArgs<'a> {
+    /// Underlying winit window.
+    pub window: &'a winit::window::Window,
+
+    /// OpenGL context connected to the window or headless surface.
+    pub context: &'a mut dyn OpenGlContext,
+}
+
+/// Arguments for [`WindowExtension::window_deinited`].
+pub struct WindowDeinitedArgs<'a> {
+    /// Underlying winit window.
+    pub window: &'a winit::window::Window,
+
+    /// OpenGL context connected to the window or headless surface.
+    pub context: &'a mut dyn OpenGlContext,
+}
+
+/// Arguments for [`WindowExtension::command`].
+pub struct WindowCommandArgs<'a> {
+    /// Underlying winit window.
+    pub window: &'a winit::window::Window,
+
+    /// OpenGL context connected to the window or headless surface.
+    pub context: &'a mut dyn OpenGlContext,
+
+    /// The command request.
+    pub request: ApiExtensionPayload,
+}
+
+/// Arguments for [`WindowExtension::event`].
+pub struct WindowEventArgs<'a> {
+    /// Underlying winit window.
+    pub window: &'a winit::window::Window,
+
+    /// OpenGL context connected to the window or headless surface.
+    pub context: &'a mut dyn OpenGlContext,
+
+    /// The event.
+    pub event: &'a winit::event::WindowEvent,
+}
+
+/// Represents a managed OpenGL context connected to a window or headless surface.
+pub trait OpenGlContext {
+    /// Context is current on the calling thread.
+    fn is_current(&self) -> bool;
+
+    /// Make context current on the calling thread.
+    fn make_current(&mut self);
+
+    /// The context.
+    fn gl(&self) -> &Rc<dyn gleam::gl::Gl>;
+
+    /// Actual render mode used to create the context.
+    fn render_mode(&self) -> RenderMode;
+
+    /// Resize surface.
+    fn resize(&mut self, size: PxSize);
+
+    /// If the context runs on the CPU, not a GPU.
+    fn is_software(&self) -> bool;
+
+    /// Swap buffers if the context is double-buffered.
+    fn swap_buffers(&mut self);
+}
+impl OpenGlContext for crate::gl::GlContext {
+    fn is_current(&self) -> bool {
+        self.is_current()
+    }
+
+    fn make_current(&mut self) {
+        self.make_current()
+    }
+
+    fn gl(&self) -> &Rc<dyn gleam::gl::Gl> {
+        self.gl()
+    }
+
+    fn render_mode(&self) -> RenderMode {
+        self.render_mode()
+    }
+
+    fn resize(&mut self, size: PxSize) {
+        self.resize(size.to_winit())
+    }
+
+    fn is_software(&self) -> bool {
+        self.is_software()
+    }
+
+    fn swap_buffers(&mut self) {
+        self.swap_buffers()
+    }
 }
 
 /// Arguments for [`RendererExtension::command`].
@@ -488,6 +647,12 @@ pub struct RendererCommandArgs<'a> {
     /// The command request.
     pub request: ApiExtensionPayload,
 
+    /// Winit window if the renderer is associated with a headed window.
+    pub window: Option<&'a winit::window::Window>,
+
+    /// OpenGL context associated with the renderer.
+    pub context: &'a mut dyn OpenGlContext,
+
     /// Redraw flag.
     ///
     /// If set to `true` the window is guaranteed to redraw the frame.
@@ -564,6 +729,30 @@ impl ViewExtensions {
         })
     }
 
+    /// Register a window extension with its own ID.
+    pub fn window<E: WindowExtension>(
+        &mut self,
+        name: impl Into<ApiExtensionName>,
+        new: impl FnMut(ApiExtensionId) -> E + Send + 'static,
+    ) -> &mut Self {
+        struct WindowExt<F>(ApiExtensionName, ApiExtensionId, F);
+        impl<E, F> ViewExtension for WindowExt<F>
+        where
+            E: WindowExtension,
+            F: FnMut(ApiExtensionId) -> E + Send + 'static,
+        {
+            fn name(&self) -> &ApiExtensionName {
+                &self.0
+            }
+
+            fn window(&mut self) -> Option<Box<dyn WindowExtension>> {
+                Some(Box::new((self.2)(self.1)))
+            }
+        }
+        self.register(move |id| WindowExt(name.into(), id, new));
+        self
+    }
+
     /// Register a renderer extension with its own ID.
     pub fn renderer<E: RendererExtension>(
         &mut self,
@@ -607,6 +796,14 @@ impl ViewExtensions {
         }
     }
 
+    pub(crate) fn new_window(&mut self) -> Vec<(ApiExtensionId, Box<dyn WindowExtension>)> {
+        self.exts
+            .iter_mut()
+            .enumerate()
+            .filter_map(|(i, e)| e.window().map(|e| (ApiExtensionId::from_index(i), e)))
+            .collect()
+    }
+
     pub(crate) fn new_renderer(&mut self) -> Vec<(ApiExtensionId, Box<dyn RendererExtension>)> {
         self.exts
             .iter_mut()
@@ -647,7 +844,7 @@ impl RendererDebugExt {
     }
 }
 impl RendererExtension for RendererDebugExt {
-    fn is_config_only(&self) -> bool {
+    fn is_init_only(&self) -> bool {
         false
     }
 
diff --git a/crates/zng-view/src/lib.rs b/crates/zng-view/src/lib.rs
index 3c222b146..888e022c0 100644
--- a/crates/zng-view/src/lib.rs
+++ b/crates/zng-view/src/lib.rs
@@ -363,7 +363,7 @@ impl winit::application::ApplicationHandler<AppEvent> for App {
 
         let mut winit_loop_guard = self.winit_loop.set(winit_loop);
 
-        self.windows[i].pump_access(&event);
+        self.windows[i].on_window_event(&event);
 
         let id = self.windows[i].id();
         let scale_factor = self.windows[i].scale_factor();
@@ -1544,6 +1544,7 @@ impl Api for App {
                 config,
                 &self.winit_loop,
                 &mut self.gl_manager,
+                self.exts.new_window(),
                 self.exts.new_renderer(),
                 self.app_sender.clone(),
             );
@@ -1959,6 +1960,19 @@ impl Api for App {
         self.exts.call_command(extension_id, extension_request)
     }
 
+    fn window_extension(
+        &mut self,
+        id: WindowId,
+        extension_id: ApiExtensionId,
+        extension_request: ApiExtensionPayload,
+    ) -> ApiExtensionPayload {
+        self.with_window(
+            id,
+            |w| w.window_extension(extension_id, extension_request),
+            || ApiExtensionPayload::invalid_request(extension_id, "window not found"),
+        )
+    }
+
     fn render_extension(
         &mut self,
         id: WindowId,
diff --git a/crates/zng-view/src/surface.rs b/crates/zng-view/src/surface.rs
index 6b5a232e0..fd2163bd3 100644
--- a/crates/zng-view/src/surface.rs
+++ b/crates/zng-view/src/surface.rs
@@ -63,7 +63,7 @@ impl fmt::Debug for Surface {
 impl Surface {
     pub fn open(
         gen: ViewProcessGen,
-        mut cfg: HeadlessRequest,
+        cfg: HeadlessRequest,
         winit_loop: &ActiveEventLoop,
         gl_manager: &mut GlContextManager,
         mut renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
@@ -72,9 +72,9 @@ impl Surface {
         let id = cfg.id;
 
         let mut context = gl_manager.create_headless(id, winit_loop, cfg.render_mode, &event_sender);
+
         let size = cfg.size.to_px(cfg.scale_factor);
         context.resize(size.to_winit());
-        let context = context;
 
         let mut opts = webrender::WebRenderOptions {
             // text-aa config from Firefox.
@@ -98,15 +98,12 @@ impl Surface {
         };
         let mut blobs = BlobExtensionsImgHandler(vec![]);
         for (id, ext) in &mut renderer_exts {
-            let cfg = cfg
-                .extensions
-                .iter()
-                .position(|(k, _)| k == id)
-                .map(|i| cfg.extensions.swap_remove(i).1);
             ext.configure(&mut RendererConfigArgs {
-                config: cfg,
+                config: cfg.extensions.iter().find(|(k, _)| k == id).map(|(_, v)| v),
                 options: &mut opts,
                 blobs: &mut blobs.0,
+                window: None,
+                context: &mut context,
             });
         }
         if !opts.enable_multithreading {
@@ -136,9 +133,10 @@ impl Surface {
                 api: &mut api,
                 document_id,
                 pipeline_id,
-                gl: &**context.gl(),
+                window: None,
+                context: &mut context,
             });
-            !ext.is_config_only()
+            !ext.is_init_only()
         });
 
         Self {
@@ -393,7 +391,7 @@ impl Surface {
                 ext.redraw(&mut RedrawArgs {
                     scale_factor: self.scale_factor,
                     size,
-                    gl: &**self.context.gl(),
+                    context: &mut self.context,
                 });
             }
 
@@ -442,7 +440,9 @@ impl Surface {
                     renderer: self.renderer.as_mut().unwrap(),
                     api: &mut self.api,
                     request,
+                    window: None,
                     redraw: &mut redraw,
+                    context: &mut self.context,
                 }));
                 break;
             }
@@ -454,7 +454,7 @@ impl Surface {
                 ext.redraw(&mut RedrawArgs {
                     scale_factor: self.scale_factor,
                     size,
-                    gl: &**self.context.gl(),
+                    context: &mut self.context,
                 });
             }
         }
@@ -478,7 +478,8 @@ impl Drop for Surface {
             ext.renderer_deinited(&mut RendererDeinitedArgs {
                 document_id: self.document_id,
                 pipeline_id: self.pipeline_id,
-                gl: &**self.context.gl(),
+                context: &mut self.context,
+                window: None,
             })
         }
     }
diff --git a/crates/zng-view/src/window.rs b/crates/zng-view/src/window.rs
index 11c95c564..99fa5c525 100644
--- a/crates/zng-view/src/window.rs
+++ b/crates/zng-view/src/window.rs
@@ -44,7 +44,8 @@ use crate::{
     display_list::{display_list_to_webrender, DisplayListCache},
     extensions::{
         self, BlobExtensionsImgHandler, DisplayListExtAdapter, FrameReadyArgs, RedrawArgs, RendererCommandArgs, RendererConfigArgs,
-        RendererDeinitedArgs, RendererExtension, RendererInitedArgs,
+        RendererDeinitedArgs, RendererExtension, RendererInitedArgs, WindowCommandArgs, WindowConfigArgs, WindowDeinitedArgs,
+        WindowExtension, WindowInitedArgs,
     },
     gl::{GlContext, GlContextManager},
     image_cache::{Image, ImageCache, ImageUseMap, WrImageCache},
@@ -71,6 +72,7 @@ pub(crate) struct Window {
     context: GlContext, // context must be dropped before window.
     window: GWindow,
     renderer: Option<Renderer>,
+    window_exts: Vec<(ApiExtensionId, Box<dyn WindowExtension>)>,
     renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
     external_images: extensions::ExternalImages,
     capture_mode: bool,
@@ -128,12 +130,14 @@ impl fmt::Debug for Window {
     }
 }
 impl Window {
+    #[allow(clippy::too_many_arguments)]
     pub fn open(
         gen: ViewProcessGen,
         icon: Option<Icon>,
-        mut cfg: WindowRequest,
+        cfg: WindowRequest,
         winit_loop: &ActiveEventLoop,
         gl_manager: &mut GlContextManager,
+        mut window_exts: Vec<(ApiExtensionId, Box<dyn WindowExtension>)>,
         mut renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
         event_sender: AppEventSender,
     ) -> Self {
@@ -227,9 +231,25 @@ impl Window {
             render_mode = RenderMode::Integrated;
         }
 
-        let (winit_window, context) = gl_manager.create_headed(id, winit, winit_loop, render_mode, &event_sender);
+        for (id, ext) in &mut window_exts {
+            ext.configure(&mut WindowConfigArgs {
+                config: cfg.extensions.iter().find(|(k, _)| k == id).map(|(_, p)| p),
+                window: &mut winit,
+            });
+        }
+
+        let (winit_window, mut context) = gl_manager.create_headed(id, winit, winit_loop, render_mode, &event_sender);
+
         render_mode = context.render_mode();
 
+        window_exts.retain_mut(|(_, ext)| {
+            ext.window_inited(&mut WindowInitedArgs {
+                window: &winit_window,
+                context: &mut context,
+            });
+            !ext.is_init_only()
+        });
+
         // * Extend the winit Windows window to not block the Alt+F4 key press.
         // * Check if the window is actually keyboard focused until first focus.
         #[cfg(windows)]
@@ -305,16 +325,12 @@ impl Window {
         };
         let mut blobs = BlobExtensionsImgHandler(vec![]);
         for (id, ext) in &mut renderer_exts {
-            let cfg = cfg
-                .extensions
-                .iter()
-                .position(|(k, _)| k == id)
-                .map(|i| cfg.extensions.swap_remove(i).1);
-
             ext.configure(&mut RendererConfigArgs {
-                config: cfg,
+                config: cfg.extensions.iter().find(|(k, _)| k == id).map(|(_, p)| p),
                 options: &mut opts,
                 blobs: &mut blobs.0,
+                window: Some(&winit_window),
+                context: &mut context,
             });
         }
         if !opts.enable_multithreading {
@@ -342,9 +358,10 @@ impl Window {
                 api: &mut api,
                 document_id,
                 pipeline_id,
-                gl: &**context.gl(),
+                window: Some(&winit_window),
+                context: &mut context,
             });
-            !ext.is_config_only()
+            !ext.is_init_only()
         });
 
         drop(wr_scope);
@@ -374,6 +391,7 @@ impl Window {
             context,
             capture_mode: cfg.capture_mode,
             renderer: Some(renderer),
+            window_exts,
             renderer_exts,
             external_images,
             video_mode: cfg.video_mode,
@@ -1471,7 +1489,7 @@ impl Window {
             ext.redraw(&mut RedrawArgs {
                 scale_factor,
                 size,
-                gl: &**self.context.gl(),
+                context: &mut self.context,
             });
         }
 
@@ -1547,6 +1565,20 @@ impl Window {
         self.render_mode
     }
 
+    /// Calls the window extension command.
+    pub fn window_extension(&mut self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> ApiExtensionPayload {
+        for (key, ext) in &mut self.window_exts {
+            if *key == extension_id {
+                return ext.command(&mut WindowCommandArgs {
+                    window: &self.window,
+                    context: &mut self.context,
+                    request,
+                });
+            }
+        }
+        ApiExtensionPayload::unknown_extension(extension_id)
+    }
+
     /// Calls the render extension command.
     pub fn render_extension(&mut self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> ApiExtensionPayload {
         for (key, ext) in &mut self.renderer_exts {
@@ -1556,6 +1588,8 @@ impl Window {
                     renderer: self.renderer.as_mut().unwrap(),
                     api: &mut self.api,
                     request,
+                    window: Some(&self.window),
+                    context: &mut self.context,
                     redraw: &mut redraw,
                 });
                 if redraw {
@@ -1694,11 +1728,18 @@ impl Window {
         });
     }
 
-    /// Pump the accessibility adapter.
-    pub fn pump_access(&mut self, event: &winit::event::WindowEvent) {
+    /// Pump the accessibility adapter and window extensions.
+    pub fn on_window_event(&mut self, event: &winit::event::WindowEvent) {
         if let Some(a) = &mut self.access {
             a.process_event(&self.window, event);
         }
+        for (_, ext) in &mut self.window_exts {
+            ext.event(&mut extensions::WindowEventArgs {
+                window: &self.window,
+                context: &mut self.context,
+                event,
+            });
+        }
     }
 
     /// Update the accessibility info.
@@ -1763,9 +1804,16 @@ impl Drop for Window {
             ext.renderer_deinited(&mut RendererDeinitedArgs {
                 document_id: self.document_id,
                 pipeline_id: self.pipeline_id,
-                gl: &**self.context.gl(),
+                context: &mut self.context,
+                window: Some(&self.window),
             })
         }
+        for (_, ext) in &mut self.window_exts {
+            ext.window_deinited(&mut WindowDeinitedArgs {
+                window: &self.window,
+                context: &mut self.context,
+            });
+        }
     }
 }
 
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index d69b973d7..d037a8073 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -11,6 +11,7 @@ lipsum = "0.9.0"
 rand = "0.8.5"
 regex = "1.10"
 serde = { version = "1.0", features = ["derive"] }
+raw-window-handle = "0.6" # matches winit use in zng-view
 
 zng = { path = "../crates/zng", features = [
     "http",
diff --git a/examples/extend_view.rs b/examples/extend_view.rs
index 7d9577a9b..e09668fa1 100644
--- a/examples/extend_view.rs
+++ b/examples/extend_view.rs
@@ -13,10 +13,10 @@ fn main() {
     examples_util::print_info();
     zng::app::crash_handler::init_debug();
 
-    // zng_view::init_extended(view_extensions);
-    // app_main();
+    zng_view::init_extended(view_extensions);
+    app_main();
 
-    zng_view::run_same_process_extended(app_main, view_extensions);
+    // zng_view::run_same_process_extended(app_main, view_extensions);
 }
 
 fn app_main() {
@@ -29,6 +29,11 @@ fn app_main() {
             title = "Extend-View Example";
             width = 900;
 
+            on_frame_image_ready = hn_once!(|_| {
+                let h = get_window_handle::app_side::get_window_handle(WINDOW.id()).unwrap();
+                tracing::info!("RAW-WINDOW-HANDLE: {h}");
+            });
+
             child = Stack! {
                 children_align = Align::CENTER;
                 direction = StackDirection::left_to_right();
@@ -112,6 +117,7 @@ fn app_main() {
 /// Called in the view-process to init extensions.
 fn view_extensions() -> ViewExtensions {
     let mut exts = ViewExtensions::new();
+    get_window_handle::view_side::extend(&mut exts);
     using_display_items::view_side::extend(&mut exts);
     using_blob::view_side::extend(&mut exts);
     using_gl_overlay::view_side::extend(&mut exts);
@@ -119,6 +125,101 @@ fn view_extensions() -> ViewExtensions {
     exts
 }
 
+/// Demo view extension window.
+pub mod get_window_handle {
+    /// App-process stuff.
+    pub mod app_side {
+        use zng::prelude::*;
+        use zng_app::view_process::VIEW_PROCESS;
+        use zng_view_api::api_extension::ApiExtensionId;
+
+        /// Get the raw-window-handle formatted to text.
+        ///
+        /// This sends a custom command to the view-process (implemented in `super::view_side`), the view-process
+        /// uses a WindowExtension to access the raw-window-handle and format it to text as a basic example.
+        pub fn get_window_handle(win_id: WindowId) -> Option<Txt> {
+            match WINDOWS.view_window_extension::<_, super::api::Response>(
+                win_id,
+                self::extension_id(),
+                &super::api::Request { alternate: false },
+            ) {
+                Ok(r) => Some(r.handle_txt),
+                Err(e) => {
+                    tracing::error!("failed to get extension response, {e}");
+                    None
+                }
+            }
+        }
+
+        pub fn extension_id() -> ApiExtensionId {
+            VIEW_PROCESS
+                .extension_id(super::api::extension_name())
+                .ok()
+                .flatten()
+                .unwrap_or(ApiExtensionId::INVALID)
+        }
+    }
+
+    /// View-process stuff, the actual extension.
+    pub mod view_side {
+        use zng::text::formatx;
+        use zng_view::extensions::{ViewExtensions, WindowExtension};
+        use zng_view_api::api_extension::{ApiExtensionId, ApiExtensionPayload};
+
+        pub fn extend(exts: &mut ViewExtensions) {
+            exts.window(super::api::extension_name(), CustomExtension::new);
+        }
+
+        struct CustomExtension {
+            id: ApiExtensionId,
+        }
+        impl CustomExtension {
+            fn new(id: ApiExtensionId) -> Self {
+                Self { id }
+            }
+        }
+        impl WindowExtension for CustomExtension {
+            fn is_init_only(&self) -> bool {
+                false
+            }
+
+            fn command(&mut self, args: &mut zng_view::extensions::WindowCommandArgs) -> ApiExtensionPayload {
+                match args.request.deserialize::<super::api::Request>() {
+                    Ok(r) => {
+                        let h = raw_window_handle::HasWindowHandle::window_handle(args.window).unwrap();
+                        ApiExtensionPayload::serialize(&super::api::Response {
+                            // note that you should only use the window handle in the view-process side.
+                            handle_txt: if r.alternate { formatx!("{h:#?}") } else { formatx!("{h:?}") },
+                        })
+                        .unwrap()
+                    }
+                    Err(e) => ApiExtensionPayload::invalid_request(self.id, format_args!("invalid command request, {e}")),
+                }
+            }
+        }
+    }
+
+    /// Shared types.
+    pub mod api {
+        use zng::text::Txt;
+        use zng_view_api::api_extension::ApiExtensionName;
+
+        pub fn extension_name() -> ApiExtensionName {
+            ApiExtensionName::new("zng.examples.extend_renderer.get_window_handle").unwrap()
+        }
+
+        #[derive(serde::Serialize, serde::Deserialize)]
+        pub struct Request {
+            pub alternate: bool,
+        }
+
+        #[derive(serde::Serialize, serde::Deserialize)]
+        pub struct Response {
+            pub handle_txt: Txt,
+        }
+    }
+}
+
 /// Demo view extension renderer, using only Webrender display items.
 pub mod using_display_items {
     /// App-process stuff, nodes.
@@ -276,7 +377,7 @@ pub mod using_display_items {
             }
         }
         impl RendererExtension for CustomExtension {
-            fn is_config_only(&self) -> bool {
+            fn is_init_only(&self) -> bool {
                 false // retain the extension after renderer creation.
             }
 
@@ -457,7 +558,7 @@ pub mod using_blob {
             }
         }
         impl RendererExtension for CustomExtension {
-            fn is_config_only(&self) -> bool {
+            fn is_init_only(&self) -> bool {
                 false // retain the extension after renderer creation.
             }
 
@@ -881,18 +982,18 @@ pub mod using_gl_overlay {
             }
         }
         impl RendererExtension for CustomExtension {
-            fn is_config_only(&self) -> bool {
+            fn is_init_only(&self) -> bool {
                 false // retain the extension after renderer creation.
             }
 
             fn renderer_inited(&mut self, args: &mut zng_view::extensions::RendererInitedArgs) {
                 // shaders/programs can be loaded here.
-                self.renderer = Some(CustomRenderer::load(args.gl));
+                self.renderer = Some(CustomRenderer::load(&**args.context.gl()));
             }
             fn renderer_deinited(&mut self, args: &mut zng_view::extensions::RendererDeinitedArgs) {
                 // ..and unloaded here.
                 if let Some(r) = self.renderer.take() {
-                    r.unload(args.gl);
+                    r.unload(&**args.context.gl());
                 }
             }
 
@@ -929,7 +1030,7 @@ pub mod using_gl_overlay {
 
             fn redraw(&mut self, args: &mut zng_view::extensions::RedrawArgs) {
                 if let Some(r) = &mut self.renderer {
-                    r.redraw(args.size, args.gl);
+                    r.redraw(args.size, &**args.context.gl());
                 }
             }
         }
@@ -1098,7 +1199,7 @@ pub mod using_gl_texture {
             }
         }
         impl RendererExtension for CustomExtension {
-            fn is_config_only(&self) -> bool {
+            fn is_init_only(&self) -> bool {
                 false // retain the extension after renderer creation.
             }
 
@@ -1111,8 +1212,8 @@ pub mod using_gl_texture {
                 let size = DeviceIntSize::splat(100);
 
                 // OpenGL
-                let texture = args.gl.gen_textures(1)[0];
-                args.gl.bind_texture(gl::TEXTURE_2D, texture);
+                let texture = args.context.gl().gen_textures(1)[0];
+                args.context.gl().bind_texture(gl::TEXTURE_2D, texture);
                 let mut img = vec![0u8; size.width as usize * size.height as usize * 4];
                 let mut line = 0u8;
                 let mut col = 0u8;
@@ -1127,7 +1228,7 @@ pub mod using_gl_texture {
                         line = line.wrapping_add(1);
                     }
                 }
-                args.gl.tex_image_2d(
+                args.context.gl().tex_image_2d(
                     gl::TEXTURE_2D,
                     0,
                     gl::RGBA as _,
@@ -1174,7 +1275,7 @@ pub mod using_gl_texture {
             fn renderer_deinited(&mut self, args: &mut zng_view::extensions::RendererDeinitedArgs) {
                 if let Some(t) = self.texture.take() {
                     let _ = t.external_id; // already cleanup by renderer deinit.
-                    args.gl.delete_textures(&[t.texture]);
+                    args.context.gl().delete_textures(&[t.texture]);
                 }
             }
 
diff --git a/examples/respawn.rs b/examples/respawn.rs
index e7a532730..814f35c8a 100644
--- a/examples/respawn.rs
+++ b/examples/respawn.rs
@@ -22,7 +22,10 @@ fn main() {
 
     // init view with extensions used to cause a crash in the view-process.
     zng_view::init_extended(test_extensions);
+    app_main();
+}
 
+fn app_main() {
     APP.defaults().run_window(async {
         Window! {
             title = "Respawn Example";


Test patch: