ext_devtools
The ext_devtools crate provides a simple API for controlling browser DevTools through the runtime:devtools module. Built as a lightweight wrapper around ext_window, it offers programmatic control over the DevTools panel used for debugging WebView windows.
Quick Start
import { open, close, isOpen } from "runtime:devtools";import { webviewNew } from "runtime:webview";
// Create window with DevTools availableconst window = await webviewNew({ title: "Debug Window", url: "app://index.html", width: 1200, height: 800, resizable: true, debug: true, // DevTools available frameless: false});
// Open DevTools programmaticallyawait open(window.id);
// Check stateconst devToolsOpen = await isOpen(window.id);console.log("DevTools open:", devToolsOpen); // true
// Close when doneawait close(window.id);API Reference
DevTools Control
open(windowId)
Opens the DevTools panel for the specified window. The DevTools panel appears as a separate docked panel or window depending on the platform and WebView implementation.
This operation is translated to WindowCmd::OpenDevTools and sent through the ext_window command channel.
Parameters:
windowId(string) - Window ID from ext_window or ext_webview
Returns: Promise<boolean> - true on success
Throws:
- Error [9100] if DevTools open fails
- Error [9101] if permission denied for window operations
import { open } from "runtime:devtools";import { webviewNew } from "runtime:webview";
// Create window with debug mode enabledconst window = await webviewNew({ title: "My App", url: "app://index.html", width: 1200, height: 800, resizable: true, debug: true, // DevTools available frameless: false});
// Open DevTools programmaticallyawait open(window.id);Development Mode Example:
import { open } from "runtime:devtools";
// Open DevTools only in development modeconst isDev = Deno.env.get("MODE") === "development";if (isDev) { await open(windowId);}close(windowId)
Closes the DevTools panel for the specified window. If the DevTools are already closed, this operation succeeds without error.
This operation is translated to WindowCmd::CloseDevTools and sent through the ext_window command channel.
Parameters:
windowId(string) - Window ID from ext_window or ext_webview
Returns: Promise<boolean> - true on success
Throws:
- Error [9100] if DevTools close fails
- Error [9101] if permission denied for window operations
import { open, close } from "runtime:devtools";
// Open DevTools for debuggingawait open(windowId);
// User completes debugging...
// Close DevTools to reclaim screen spaceawait close(windowId);Conditional Close:
import { close, isOpen } from "runtime:devtools";
// Close DevTools if openif (await isOpen(windowId)) { await close(windowId);}isOpen(windowId)
Checks if the DevTools panel is currently open for the specified window. Returns true if DevTools are open, false otherwise.
This operation is translated to WindowCmd::IsDevToolsOpen and sent through the ext_window command channel.
Parameters:
windowId(string) - Window ID from ext_window or ext_webview
Returns: Promise<boolean> - true if DevTools are open, false otherwise
Throws:
- Error [9100] if state query fails
- Error [9101] if permission denied for window operations
import { isOpen } from "runtime:devtools";
// Check DevTools stateconst devToolsOpen = await isOpen(windowId);console.log("DevTools open:", devToolsOpen);UI State Example:
import { isOpen } from "runtime:devtools";
// Conditional UI state based on DevToolsconst devToolsButton = document.getElementById("devtools-toggle");if (await isOpen(windowId)) { devToolsButton.textContent = "Close DevTools"; devToolsButton.classList.add("active");} else { devToolsButton.textContent = "Open DevTools"; devToolsButton.classList.remove("active");}Common Patterns
Toggle DevTools
import { open, close, isOpen } from "runtime:devtools";
async function toggleDevTools(windowId: string) { if (await isOpen(windowId)) { await close(windowId); console.log("DevTools closed"); } else { await open(windowId); console.log("DevTools opened"); }}
// Use with keyboard shortcut or buttonawait toggleDevTools(windowId);DevTools Keyboard Shortcut
import { open, close, isOpen } from "runtime:devtools";import { on } from "runtime:shortcuts";
// F12 to toggle DevToolsawait on("F12", async () => { if (await isOpen(currentWindowId)) { await close(currentWindowId); } else { await open(currentWindowId); }});Conditional DevTools (Development Mode)
import { open } from "runtime:devtools";
const isDev = Deno.env.get("MODE") === "development";
if (isDev) { await open(windowId);}UI State Based on DevTools
import { isOpen } from "runtime:devtools";
// Update UI to reflect DevTools stateconst devToolsButton = document.getElementById("devtools-toggle");
if (await isOpen(windowId)) { devToolsButton.textContent = "Close DevTools"; devToolsButton.classList.add("active");} else { devToolsButton.textContent = "Open DevTools"; devToolsButton.classList.remove("active");}DevTools with Event Handler
import { open, close, isOpen } from "runtime:devtools";
// Button to toggle DevToolsdocument.getElementById("devtools-toggle")?.addEventListener("click", async () => { const currentState = await isOpen(windowId); if (currentState) { await close(windowId); } else { await open(windowId); }});Architecture
ext_devtools is a thin wrapper around ext_window:
TypeScript Application ↓runtime:devtools (open, close, isOpen) ↓ (translates to WindowCmd messages)runtime:window ↓ (wry DevTools API)Native WebView DevToolsAll DevTools operations are translated to window commands:
| DevTools Operation | Window Command | Description |
|---|---|---|
open() | WindowCmd::OpenDevTools | Open DevTools panel |
close() | WindowCmd::CloseDevTools | Close DevTools panel |
isOpen() | WindowCmd::IsDevToolsOpen | Check DevTools state |
Implementation Details
Opening DevTools
open() sends WindowCmd::OpenDevTools through the window command channel:
- Check permissions via
check_window_caps() - Send
WindowCmd::OpenDevToolsto ext_window command channel - Await response confirmation
- Return
trueon success
Closing DevTools
close() sends WindowCmd::CloseDevTools:
- Check permissions
- Send
WindowCmd::CloseDevToolswith window ID - Await response confirmation
- Return
trueon success
Checking State
isOpen() queries the DevTools state:
- Check permissions
- Send
WindowCmd::IsDevToolsOpenwith window ID - Await boolean response from window manager
- Return DevTools open state (defaults to
falseif query fails)
Error Handling
All operations use structured error codes:
| Code | Error | Description |
|---|---|---|
| 9100 | Generic | General DevTools operation error |
| 9101 | PermissionDenied | Window management permission denied |
import { open, close, isOpen } from "runtime:devtools";
// Handle open errorstry { await open(windowId);} catch (error) { // Error 9100: Operation failed // Error 9101: Permission denied console.error("Failed to open DevTools:", error);}
// Handle state query errorstry { const devToolsOpen = await isOpen(windowId);} catch (error) { // Error 9100: Query failed console.error("Failed to check DevTools state:", error);}Permissions
DevTools operations require window management permissions in manifest.app.toml:
[permissions.ui]windows = true # Required for DevTools operationsOperations fail with error 9101 if permissions are not granted.
Platform Support
| Platform | DevTools Backend | Status |
|---|---|---|
| macOS (x64) | WebKit Inspector | ✅ Full support |
| macOS (ARM) | WebKit Inspector | ✅ Full support |
| Windows (x64) | Edge DevTools (F12) | ✅ Full support |
| Windows (ARM) | Edge DevTools (F12) | ✅ Full support |
| Linux (x64) | WebKit Inspector | ✅ Full support |
| Linux (ARM) | WebKit Inspector | ✅ Full support |
Platform-specific DevTools behavior is handled by the underlying wry crate.
Common Pitfalls
1. Using Invalid Window IDs
// ❌ ERROR: Using ID of destroyed windowawait windowExit(windowId);await open(windowId); // Window no longer exists
// ✅ CORRECT: Only use DevTools with valid windowsawait open(windowId);// ... later ...await windowExit(windowId); // Close window last2. Missing Debug Flag
// ❌ INCORRECT: DevTools may not be availableconst window = await webviewNew({ title: "App", url: "app://index.html", width: 800, height: 600, resizable: true, debug: false, // DevTools disabled! frameless: false});await open(window.id); // May fail or have no effect
// ✅ CORRECT: Enable debug mode for DevToolsconst window = await webviewNew({ title: "App", url: "app://index.html", width: 800, height: 600, resizable: true, debug: true, // DevTools available frameless: false});await open(window.id); // Works3. Not Checking State Before Toggle
// ❌ RISKY: Assuming current stateawait close(windowId); // What if already closed?
// ✅ CORRECT: Check state firstif (await isOpen(windowId)) { await close(windowId);}
// ✅ BETTER: Use toggle patternasync function toggleDevTools(windowId: string) { if (await isOpen(windowId)) { await close(windowId); } else { await open(windowId); }}Related
- ext_window - Window management extension
- ext_webview - WebView creation extension
- ext_shortcuts - Keyboard shortcuts
- Architecture - System architecture