electrobun 1.12.3 → 1.13.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/bun/core/BrowserView.ts +10 -0
- package/dist/api/bun/core/Updater.ts +17 -4
- package/dist/api/bun/preload/.generated/compiled.ts +1 -1
- package/dist/api/bun/preload/webviewTag.ts +22 -0
- package/dist/api/bun/proc/native.ts +21 -0
- package/package.json +1 -1
- package/src/cli/index.ts +0 -1
|
@@ -40,6 +40,10 @@ export type BrowserViewOptions<T = undefined> = {
|
|
|
40
40
|
// Use for untrusted content (remote URLs) to prevent malicious sites from
|
|
41
41
|
// accessing internal APIs, creating OOPIFs, or communicating with Bun
|
|
42
42
|
sandbox: boolean;
|
|
43
|
+
// Set transparent on the AbstractView at creation (before first paint)
|
|
44
|
+
startTransparent: boolean;
|
|
45
|
+
// Set passthrough on the AbstractView at creation (before first paint)
|
|
46
|
+
startPassthrough: boolean;
|
|
43
47
|
// renderer:
|
|
44
48
|
};
|
|
45
49
|
|
|
@@ -93,6 +97,8 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
93
97
|
navigationRules: string | null = null;
|
|
94
98
|
// Sandbox mode disables RPC and only allows event emission (for untrusted content)
|
|
95
99
|
sandbox: boolean = false;
|
|
100
|
+
startTransparent: boolean = false;
|
|
101
|
+
startPassthrough: boolean = false;
|
|
96
102
|
|
|
97
103
|
constructor(options: Partial<BrowserViewOptions<T>> = defaultOptions) {
|
|
98
104
|
// const rpc = options.rpc;
|
|
@@ -118,6 +124,8 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
118
124
|
this.navigationRules = options.navigationRules || null;
|
|
119
125
|
this.renderer = options.renderer ?? defaultOptions.renderer ?? "native";
|
|
120
126
|
this.sandbox = options.sandbox ?? false;
|
|
127
|
+
this.startTransparent = options.startTransparent ?? false;
|
|
128
|
+
this.startPassthrough = options.startPassthrough ?? false;
|
|
121
129
|
|
|
122
130
|
BrowserViewMap[this.id] = this;
|
|
123
131
|
this.ptr = this.init() as Pointer;
|
|
@@ -167,6 +175,8 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
167
175
|
autoResize: this.autoResize,
|
|
168
176
|
navigationRules: this.navigationRules,
|
|
169
177
|
sandbox: this.sandbox,
|
|
178
|
+
startTransparent: this.startTransparent,
|
|
179
|
+
startPassthrough: this.startPassthrough,
|
|
170
180
|
// transparent is looked up from parent window in native.ts
|
|
171
181
|
});
|
|
172
182
|
}
|
|
@@ -1039,11 +1039,24 @@ del "%~f0"
|
|
|
1039
1039
|
|
|
1040
1040
|
// Cross-platform app launch (Windows is handled above with its own update script)
|
|
1041
1041
|
if (currentOS === "macos") {
|
|
1042
|
-
//
|
|
1042
|
+
// Wait for the current process to fully exit before relaunching.
|
|
1043
|
+
// macOS 'open' on an already-running app just activates the existing
|
|
1044
|
+
// instance instead of launching a new one, so we must ensure the
|
|
1045
|
+
// current process has exited first. The detached shell survives our
|
|
1046
|
+
// exit and polls until the process is gone.
|
|
1047
|
+
const pid = process.pid;
|
|
1043
1048
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1044
|
-
Bun.spawn(
|
|
1045
|
-
|
|
1046
|
-
|
|
1049
|
+
Bun.spawn(
|
|
1050
|
+
[
|
|
1051
|
+
"sh",
|
|
1052
|
+
"-c",
|
|
1053
|
+
`while kill -0 ${pid} 2>/dev/null; do sleep 0.5; done; sleep 1; open "${runningAppBundlePath}"`,
|
|
1054
|
+
],
|
|
1055
|
+
{
|
|
1056
|
+
detached: true,
|
|
1057
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
1058
|
+
} as any,
|
|
1059
|
+
);
|
|
1047
1060
|
} else if (currentOS === "linux") {
|
|
1048
1061
|
// On Linux, launch the launcher binary inside the app directory
|
|
1049
1062
|
const launcherPath = join(runningAppBundlePath, "bin", "launcher");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
|
|
3
3
|
|
|
4
4
|
// Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
|
|
5
|
-
export const preloadScript = "(() => {\n // src/bun/preload/encryption.ts\n function base64ToUint8Array(base64) {\n return new Uint8Array(atob(base64).split(\"\").map((char) => char.charCodeAt(0)));\n }\n function uint8ArrayToBase64(uint8Array) {\n let binary = \"\";\n for (let i = 0;i < uint8Array.length; i++) {\n binary += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binary);\n }\n async function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", rawKey, { name: \"AES-GCM\" }, true, [\"encrypt\", \"decrypt\"]);\n }\n async function initEncryption() {\n const secretKey = await generateKeyFromBytes(new Uint8Array(window.__electrobunSecretKeyBytes));\n const encryptString = async (plaintext) => {\n const encoder = new TextEncoder;\n const encodedText = encoder.encode(plaintext);\n const iv = window.crypto.getRandomValues(new Uint8Array(12));\n const encryptedBuffer = await window.crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, secretKey, encodedText);\n const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));\n const tag = new Uint8Array(encryptedBuffer.slice(-16));\n return {\n encryptedData: uint8ArrayToBase64(encryptedData),\n iv: uint8ArrayToBase64(iv),\n tag: uint8ArrayToBase64(tag)\n };\n };\n const decryptString = async (encryptedDataB64, ivB64, tagB64) => {\n const encryptedData = base64ToUint8Array(encryptedDataB64);\n const iv = base64ToUint8Array(ivB64);\n const tag = base64ToUint8Array(tagB64);\n const combinedData = new Uint8Array(encryptedData.length + tag.length);\n combinedData.set(encryptedData);\n combinedData.set(tag, encryptedData.length);\n const decryptedBuffer = await window.crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, secretKey, combinedData);\n const decoder = new TextDecoder;\n return decoder.decode(decryptedBuffer);\n };\n window.__electrobun_encrypt = encryptString;\n window.__electrobun_decrypt = decryptString;\n }\n\n // src/bun/preload/internalRpc.ts\n var pendingRequests = {};\n var requestId = 0;\n var isProcessingQueue = false;\n var sendQueue = [];\n function processQueue() {\n if (isProcessingQueue) {\n setTimeout(processQueue);\n return;\n }\n if (sendQueue.length === 0)\n return;\n isProcessingQueue = true;\n const batch = JSON.stringify(sendQueue);\n sendQueue.length = 0;\n window.__electrobunInternalBridge?.postMessage(batch);\n setTimeout(() => {\n isProcessingQueue = false;\n }, 2);\n }\n function send(type, payload) {\n sendQueue.push(JSON.stringify({ type: \"message\", id: type, payload }));\n processQueue();\n }\n function request(type, payload) {\n return new Promise((resolve, reject) => {\n const id = `req_${++requestId}_${Date.now()}`;\n pendingRequests[id] = { resolve, reject };\n sendQueue.push(JSON.stringify({\n type: \"request\",\n method: type,\n id,\n params: payload,\n hostWebviewId: window.__electrobunWebviewId\n }));\n processQueue();\n setTimeout(() => {\n if (pendingRequests[id]) {\n delete pendingRequests[id];\n reject(new Error(`Request timeout: ${type}`));\n }\n }, 1e4);\n });\n }\n function handleResponse(msg) {\n if (msg && msg.type === \"response\" && msg.id) {\n const pending = pendingRequests[msg.id];\n if (pending) {\n delete pendingRequests[msg.id];\n if (msg.success)\n pending.resolve(msg.payload);\n else\n pending.reject(msg.payload);\n }\n }\n }\n\n // src/bun/preload/dragRegions.ts\n function isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n const draggableByStyle = target.closest('[style*=\"app-region\"][style*=\"drag\"]');\n const draggableByClass = target.closest(\".electrobun-webkit-app-region-drag\");\n return !!(draggableByStyle || draggableByClass);\n }\n function initDragRegions() {\n document.addEventListener(\"mousedown\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"startWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n }\n\n // src/bun/preload/webviewTag.ts\n var webviewRegistry = {};\n\n class ElectrobunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n lastRect = { x: 0, y: 0, width: 0, height: 0 };\n resizeObserver = null;\n positionCheckLoop = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this.resizeObserver)\n this.resizeObserver.disconnect();\n if (this.positionCheckLoop)\n clearInterval(this.positionCheckLoop);\n }\n async initWebview() {\n const rect = this.getBoundingClientRect();\n this.lastRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const url = this.getAttribute(\"src\");\n const html = this.getAttribute(\"html\");\n const preload = this.getAttribute(\"preload\");\n const partition = this.getAttribute(\"partition\");\n const renderer = this.getAttribute(\"renderer\") || \"native\";\n const masks = this.getAttribute(\"masks\");\n const sandbox = this.hasAttribute(\"sandbox\");\n this.sandboxed = sandbox;\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n try {\n const webviewId = await request(\"webviewTagInit\", {\n hostWebviewId: window.__electrobunWebviewId,\n windowId: window.__electrobunWindowId,\n renderer,\n url,\n html,\n preload,\n partition,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n navigationRules: null,\n sandbox\n });\n this.webviewId = webviewId;\n this.id = `electrobun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers();\n this.syncDimensions(true);\n } catch (err) {\n console.error(\"Failed to init webview:\", err);\n }\n }\n setupObservers() {\n this.resizeObserver = new ResizeObserver(() => this.syncDimensions());\n this.resizeObserver.observe(this);\n this.positionCheckLoop = setInterval(() => this.syncDimensions(), 100);\n }\n syncDimensions(force = false) {\n if (this.webviewId === null)\n return;\n const rect = this.getBoundingClientRect();\n const newRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n if (!force && newRect.x === this.lastRect.x && newRect.y === this.lastRect.y && newRect.width === this.lastRect.width && newRect.height === this.lastRect.height) {\n return;\n }\n this.lastRect = newRect;\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: newRect,\n masks: JSON.stringify(masks)\n });\n }\n loadURL(url) {\n if (this.webviewId === null)\n return;\n this.setAttribute(\"src\", url);\n send(\"webviewTagUpdateSrc\", { id: this.webviewId, url });\n }\n loadHTML(html) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagUpdateHtml\", { id: this.webviewId, html });\n }\n reload() {\n if (this.webviewId !== null)\n send(\"webviewTagReload\", { id: this.webviewId });\n }\n goBack() {\n if (this.webviewId !== null)\n send(\"webviewTagGoBack\", { id: this.webviewId });\n }\n goForward() {\n if (this.webviewId !== null)\n send(\"webviewTagGoForward\", { id: this.webviewId });\n }\n async canGoBack() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoBack\", {\n id: this.webviewId\n });\n }\n async canGoForward() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoForward\", {\n id: this.webviewId\n });\n }\n toggleTransparent(value) {\n if (this.webviewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n send(\"webviewTagSetTransparent\", {\n id: this.webviewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.webviewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n send(\"webviewTagSetPassthrough\", {\n id: this.webviewId,\n enablePassthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.webviewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"webviewTagSetHidden\", { id: this.webviewId, hidden: this.hidden });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n setNavigationRules(rules) {\n if (this.webviewId !== null) {\n send(\"webviewTagSetNavigationRules\", { id: this.webviewId, rules });\n }\n }\n findInPage(searchText, options) {\n if (this.webviewId === null)\n return;\n const forward = options?.forward !== false;\n const matchCase = options?.matchCase || false;\n send(\"webviewTagFindInPage\", {\n id: this.webviewId,\n searchText,\n forward,\n matchCase\n });\n }\n stopFindInPage() {\n if (this.webviewId !== null)\n send(\"webviewTagStopFind\", { id: this.webviewId });\n }\n openDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagOpenDevTools\", { id: this.webviewId });\n }\n closeDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagCloseDevTools\", { id: this.webviewId });\n }\n toggleDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagToggleDevTools\", { id: this.webviewId });\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n if (this.webviewId !== null)\n this.loadURL(value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n get html() {\n return this.getAttribute(\"html\");\n }\n set html(value) {\n if (value) {\n this.setAttribute(\"html\", value);\n if (this.webviewId !== null)\n this.loadHTML(value);\n } else {\n this.removeAttribute(\"html\");\n }\n }\n get preload() {\n return this.getAttribute(\"preload\");\n }\n set preload(value) {\n if (value)\n this.setAttribute(\"preload\", value);\n else\n this.removeAttribute(\"preload\");\n }\n get renderer() {\n return this.getAttribute(\"renderer\") || \"native\";\n }\n set renderer(value) {\n this.setAttribute(\"renderer\", value);\n }\n get sandbox() {\n return this.sandboxed;\n }\n }\n function initWebviewTag() {\n if (!customElements.get(\"electrobun-webview\")) {\n customElements.define(\"electrobun-webview\", ElectrobunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-webview {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #fff;\n\tbackground-repeat: no-repeat !important;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n }\n\n // src/bun/preload/events.ts\n function emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__electrobunWebviewId,\n eventName,\n detail\n }\n }));\n });\n }\n function initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n }\n var cmdKeyHeld = false;\n var cmdKeyTimestamp = 0;\n var CMD_KEY_THRESHOLD_MS = 500;\n function isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n }\n function initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n }\n function initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n }\n function initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n }\n\n // src/bun/preload/index.ts\n initEncryption().catch((err) => console.error(\"Failed to initialize encryption:\", err));\n var internalMessageHandler = (msg) => {\n handleResponse(msg);\n };\n if (!window.__electrobun) {\n window.__electrobun = {\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n }\n };\n } else {\n window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__electrobun.receiveMessageFromBun = (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n };\n }\n window.__electrobunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n };\n initLifecycleEvents();\n initCmdClickHandling();\n initSPANavigationInterception();\n initOverscrollPrevention();\n initDragRegions();\n initWebviewTag();\n})();\n";
|
|
5
|
+
export const preloadScript = "(() => {\n // src/bun/preload/encryption.ts\n function base64ToUint8Array(base64) {\n return new Uint8Array(atob(base64).split(\"\").map((char) => char.charCodeAt(0)));\n }\n function uint8ArrayToBase64(uint8Array) {\n let binary = \"\";\n for (let i = 0;i < uint8Array.length; i++) {\n binary += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binary);\n }\n async function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", rawKey, { name: \"AES-GCM\" }, true, [\"encrypt\", \"decrypt\"]);\n }\n async function initEncryption() {\n const secretKey = await generateKeyFromBytes(new Uint8Array(window.__electrobunSecretKeyBytes));\n const encryptString = async (plaintext) => {\n const encoder = new TextEncoder;\n const encodedText = encoder.encode(plaintext);\n const iv = window.crypto.getRandomValues(new Uint8Array(12));\n const encryptedBuffer = await window.crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, secretKey, encodedText);\n const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));\n const tag = new Uint8Array(encryptedBuffer.slice(-16));\n return {\n encryptedData: uint8ArrayToBase64(encryptedData),\n iv: uint8ArrayToBase64(iv),\n tag: uint8ArrayToBase64(tag)\n };\n };\n const decryptString = async (encryptedDataB64, ivB64, tagB64) => {\n const encryptedData = base64ToUint8Array(encryptedDataB64);\n const iv = base64ToUint8Array(ivB64);\n const tag = base64ToUint8Array(tagB64);\n const combinedData = new Uint8Array(encryptedData.length + tag.length);\n combinedData.set(encryptedData);\n combinedData.set(tag, encryptedData.length);\n const decryptedBuffer = await window.crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, secretKey, combinedData);\n const decoder = new TextDecoder;\n return decoder.decode(decryptedBuffer);\n };\n window.__electrobun_encrypt = encryptString;\n window.__electrobun_decrypt = decryptString;\n }\n\n // src/bun/preload/internalRpc.ts\n var pendingRequests = {};\n var requestId = 0;\n var isProcessingQueue = false;\n var sendQueue = [];\n function processQueue() {\n if (isProcessingQueue) {\n setTimeout(processQueue);\n return;\n }\n if (sendQueue.length === 0)\n return;\n isProcessingQueue = true;\n const batch = JSON.stringify(sendQueue);\n sendQueue.length = 0;\n window.__electrobunInternalBridge?.postMessage(batch);\n setTimeout(() => {\n isProcessingQueue = false;\n }, 2);\n }\n function send(type, payload) {\n sendQueue.push(JSON.stringify({ type: \"message\", id: type, payload }));\n processQueue();\n }\n function request(type, payload) {\n return new Promise((resolve, reject) => {\n const id = `req_${++requestId}_${Date.now()}`;\n pendingRequests[id] = { resolve, reject };\n sendQueue.push(JSON.stringify({\n type: \"request\",\n method: type,\n id,\n params: payload,\n hostWebviewId: window.__electrobunWebviewId\n }));\n processQueue();\n setTimeout(() => {\n if (pendingRequests[id]) {\n delete pendingRequests[id];\n reject(new Error(`Request timeout: ${type}`));\n }\n }, 1e4);\n });\n }\n function handleResponse(msg) {\n if (msg && msg.type === \"response\" && msg.id) {\n const pending = pendingRequests[msg.id];\n if (pending) {\n delete pendingRequests[msg.id];\n if (msg.success)\n pending.resolve(msg.payload);\n else\n pending.reject(msg.payload);\n }\n }\n }\n\n // src/bun/preload/dragRegions.ts\n function isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n const draggableByStyle = target.closest('[style*=\"app-region\"][style*=\"drag\"]');\n const draggableByClass = target.closest(\".electrobun-webkit-app-region-drag\");\n return !!(draggableByStyle || draggableByClass);\n }\n function initDragRegions() {\n document.addEventListener(\"mousedown\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"startWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n }\n\n // src/bun/preload/webviewTag.ts\n var webviewRegistry = {};\n\n class ElectrobunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n lastRect = { x: 0, y: 0, width: 0, height: 0 };\n resizeObserver = null;\n positionCheckLoop = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this.resizeObserver)\n this.resizeObserver.disconnect();\n if (this.positionCheckLoop)\n clearInterval(this.positionCheckLoop);\n }\n async initWebview() {\n const rect = this.getBoundingClientRect();\n this.lastRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const url = this.getAttribute(\"src\");\n const html = this.getAttribute(\"html\");\n const preload = this.getAttribute(\"preload\");\n const partition = this.getAttribute(\"partition\");\n const renderer = this.getAttribute(\"renderer\") || \"native\";\n const masks = this.getAttribute(\"masks\");\n const sandbox = this.hasAttribute(\"sandbox\");\n this.sandboxed = sandbox;\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n try {\n const webviewId = await request(\"webviewTagInit\", {\n hostWebviewId: window.__electrobunWebviewId,\n windowId: window.__electrobunWindowId,\n renderer,\n url,\n html,\n preload,\n partition,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n navigationRules: null,\n sandbox,\n transparent,\n passthrough\n });\n this.webviewId = webviewId;\n this.id = `electrobun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers();\n this.syncDimensions(true);\n requestAnimationFrame(() => {\n Object.values(webviewRegistry).forEach((webview) => {\n if (webview !== this && webview.webviewId !== null) {\n webview.syncDimensions(true);\n }\n });\n });\n } catch (err) {\n console.error(\"Failed to init webview:\", err);\n }\n }\n setupObservers() {\n this.resizeObserver = new ResizeObserver(() => this.syncDimensions());\n this.resizeObserver.observe(this);\n this.positionCheckLoop = setInterval(() => this.syncDimensions(), 100);\n }\n syncDimensions(force = false) {\n if (this.webviewId === null)\n return;\n const rect = this.getBoundingClientRect();\n const newRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n if (!force && newRect.x === this.lastRect.x && newRect.y === this.lastRect.y && newRect.width === this.lastRect.width && newRect.height === this.lastRect.height) {\n return;\n }\n this.lastRect = newRect;\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: newRect,\n masks: JSON.stringify(masks)\n });\n }\n loadURL(url) {\n if (this.webviewId === null)\n return;\n this.setAttribute(\"src\", url);\n send(\"webviewTagUpdateSrc\", { id: this.webviewId, url });\n }\n loadHTML(html) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagUpdateHtml\", { id: this.webviewId, html });\n }\n reload() {\n if (this.webviewId !== null)\n send(\"webviewTagReload\", { id: this.webviewId });\n }\n goBack() {\n if (this.webviewId !== null)\n send(\"webviewTagGoBack\", { id: this.webviewId });\n }\n goForward() {\n if (this.webviewId !== null)\n send(\"webviewTagGoForward\", { id: this.webviewId });\n }\n async canGoBack() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoBack\", {\n id: this.webviewId\n });\n }\n async canGoForward() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoForward\", {\n id: this.webviewId\n });\n }\n toggleTransparent(value) {\n if (this.webviewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"webviewTagSetTransparent\", {\n id: this.webviewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.webviewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"webviewTagSetPassthrough\", {\n id: this.webviewId,\n enablePassthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.webviewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"webviewTagSetHidden\", { id: this.webviewId, hidden: this.hidden });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n setNavigationRules(rules) {\n if (this.webviewId !== null) {\n send(\"webviewTagSetNavigationRules\", { id: this.webviewId, rules });\n }\n }\n findInPage(searchText, options) {\n if (this.webviewId === null)\n return;\n const forward = options?.forward !== false;\n const matchCase = options?.matchCase || false;\n send(\"webviewTagFindInPage\", {\n id: this.webviewId,\n searchText,\n forward,\n matchCase\n });\n }\n stopFindInPage() {\n if (this.webviewId !== null)\n send(\"webviewTagStopFind\", { id: this.webviewId });\n }\n openDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagOpenDevTools\", { id: this.webviewId });\n }\n closeDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagCloseDevTools\", { id: this.webviewId });\n }\n toggleDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagToggleDevTools\", { id: this.webviewId });\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n if (this.webviewId !== null)\n this.loadURL(value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n get html() {\n return this.getAttribute(\"html\");\n }\n set html(value) {\n if (value) {\n this.setAttribute(\"html\", value);\n if (this.webviewId !== null)\n this.loadHTML(value);\n } else {\n this.removeAttribute(\"html\");\n }\n }\n get preload() {\n return this.getAttribute(\"preload\");\n }\n set preload(value) {\n if (value)\n this.setAttribute(\"preload\", value);\n else\n this.removeAttribute(\"preload\");\n }\n get renderer() {\n return this.getAttribute(\"renderer\") || \"native\";\n }\n set renderer(value) {\n this.setAttribute(\"renderer\", value);\n }\n get sandbox() {\n return this.sandboxed;\n }\n }\n function initWebviewTag() {\n if (!customElements.get(\"electrobun-webview\")) {\n customElements.define(\"electrobun-webview\", ElectrobunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-webview {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #fff;\n\tbackground-repeat: no-repeat !important;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n }\n\n // src/bun/preload/events.ts\n function emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__electrobunWebviewId,\n eventName,\n detail\n }\n }));\n });\n }\n function initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n }\n var cmdKeyHeld = false;\n var cmdKeyTimestamp = 0;\n var CMD_KEY_THRESHOLD_MS = 500;\n function isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n }\n function initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n }\n function initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n }\n function initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n }\n\n // src/bun/preload/index.ts\n initEncryption().catch((err) => console.error(\"Failed to initialize encryption:\", err));\n var internalMessageHandler = (msg) => {\n handleResponse(msg);\n };\n if (!window.__electrobun) {\n window.__electrobun = {\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n }\n };\n } else {\n window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__electrobun.receiveMessageFromBun = (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n };\n }\n window.__electrobunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n };\n initLifecycleEvents();\n initCmdClickHandling();\n initSPANavigationInterception();\n initOverscrollPrevention();\n initDragRegions();\n initWebviewTag();\n})();\n";
|
|
6
6
|
|
|
7
7
|
// Minimal preload for sandboxed/untrusted webviews (lifecycle events only, no RPC)
|
|
8
8
|
export const preloadScriptSandboxed = "(() => {\n // src/bun/preload/events.ts\n function emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__electrobunWebviewId,\n eventName,\n detail\n }\n }));\n });\n }\n function initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n }\n var cmdKeyHeld = false;\n var cmdKeyTimestamp = 0;\n var CMD_KEY_THRESHOLD_MS = 500;\n function isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n }\n function initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n }\n function initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n }\n function initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n }\n\n // src/bun/preload/index-sandboxed.ts\n initLifecycleEvents();\n initCmdClickHandling();\n initSPANavigationInterception();\n initOverscrollPrevention();\n})();\n";
|
|
@@ -81,6 +81,13 @@ export class ElectrobunWebviewTag extends HTMLElement {
|
|
|
81
81
|
// Sandbox attribute: when present, the child webview is sandboxed (no RPC, events only)
|
|
82
82
|
const sandbox = this.hasAttribute("sandbox");
|
|
83
83
|
this.sandboxed = sandbox;
|
|
84
|
+
// Read transparent/passthrough attributes for initial state (avoids flash)
|
|
85
|
+
const transparent = this.hasAttribute("transparent");
|
|
86
|
+
const passthrough = this.hasAttribute("passthrough");
|
|
87
|
+
this.transparent = transparent;
|
|
88
|
+
this.passthroughEnabled = passthrough;
|
|
89
|
+
if (transparent) this.style.opacity = "0";
|
|
90
|
+
if (passthrough) this.style.pointerEvents = "none";
|
|
84
91
|
|
|
85
92
|
if (masks) {
|
|
86
93
|
masks.split(",").forEach((s) => this.maskSelectors.add(s.trim()));
|
|
@@ -103,6 +110,8 @@ export class ElectrobunWebviewTag extends HTMLElement {
|
|
|
103
110
|
},
|
|
104
111
|
navigationRules: null,
|
|
105
112
|
sandbox,
|
|
113
|
+
transparent,
|
|
114
|
+
passthrough,
|
|
106
115
|
})) as number;
|
|
107
116
|
|
|
108
117
|
this.webviewId = webviewId;
|
|
@@ -112,6 +121,17 @@ export class ElectrobunWebviewTag extends HTMLElement {
|
|
|
112
121
|
this.setupObservers();
|
|
113
122
|
// Force immediate sync after initialization
|
|
114
123
|
this.syncDimensions(true);
|
|
124
|
+
|
|
125
|
+
// When adding a new webview, force all existing webviews to re-sync their positions
|
|
126
|
+
// This handles layout changes caused by the new webview
|
|
127
|
+
// Use requestAnimationFrame to ensure DOM layout is complete
|
|
128
|
+
requestAnimationFrame(() => {
|
|
129
|
+
Object.values(webviewRegistry).forEach(webview => {
|
|
130
|
+
if (webview !== this && webview.webviewId !== null) {
|
|
131
|
+
webview.syncDimensions(true);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
115
135
|
} catch (err) {
|
|
116
136
|
console.error("Failed to init webview:", err);
|
|
117
137
|
}
|
|
@@ -219,6 +239,7 @@ export class ElectrobunWebviewTag extends HTMLElement {
|
|
|
219
239
|
toggleTransparent(value?: boolean) {
|
|
220
240
|
if (this.webviewId === null) return;
|
|
221
241
|
this.transparent = value !== undefined ? value : !this.transparent;
|
|
242
|
+
this.style.opacity = this.transparent ? "0" : "";
|
|
222
243
|
send("webviewTagSetTransparent", {
|
|
223
244
|
id: this.webviewId,
|
|
224
245
|
transparent: this.transparent,
|
|
@@ -229,6 +250,7 @@ export class ElectrobunWebviewTag extends HTMLElement {
|
|
|
229
250
|
if (this.webviewId === null) return;
|
|
230
251
|
this.passthroughEnabled =
|
|
231
252
|
value !== undefined ? value : !this.passthroughEnabled;
|
|
253
|
+
this.style.pointerEvents = this.passthroughEnabled ? "none" : "";
|
|
232
254
|
send("webviewTagSetPassthrough", {
|
|
233
255
|
id: this.webviewId,
|
|
234
256
|
enablePassthrough: this.passthroughEnabled,
|
|
@@ -196,6 +196,14 @@ export const native = (() => {
|
|
|
196
196
|
],
|
|
197
197
|
returns: FFIType.ptr,
|
|
198
198
|
},
|
|
199
|
+
// Pre-set flags for the next initWebview call (workaround for FFI param count limits)
|
|
200
|
+
setNextWebviewFlags: {
|
|
201
|
+
args: [
|
|
202
|
+
FFIType.bool, // startTransparent
|
|
203
|
+
FFIType.bool, // startPassthrough
|
|
204
|
+
],
|
|
205
|
+
returns: FFIType.void,
|
|
206
|
+
},
|
|
199
207
|
|
|
200
208
|
// webviewtag
|
|
201
209
|
webviewCanGoBack: {
|
|
@@ -903,7 +911,10 @@ export const ffi = {
|
|
|
903
911
|
autoResize: boolean;
|
|
904
912
|
navigationRules: string | null;
|
|
905
913
|
sandbox: boolean;
|
|
914
|
+
startTransparent: boolean;
|
|
915
|
+
startPassthrough: boolean;
|
|
906
916
|
}): FFIType.ptr => {
|
|
917
|
+
|
|
907
918
|
const {
|
|
908
919
|
id,
|
|
909
920
|
windowId,
|
|
@@ -919,6 +930,8 @@ export const ffi = {
|
|
|
919
930
|
frame: { x, y, width, height },
|
|
920
931
|
autoResize,
|
|
921
932
|
sandbox,
|
|
933
|
+
startTransparent,
|
|
934
|
+
startPassthrough,
|
|
922
935
|
} = params;
|
|
923
936
|
|
|
924
937
|
const parentWindow = BrowserWindow.getById(windowId);
|
|
@@ -971,6 +984,8 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
|
|
|
971
984
|
|
|
972
985
|
const customPreload = preload;
|
|
973
986
|
|
|
987
|
+
// Pre-set flags before initWebview (workaround for FFI param count limits)
|
|
988
|
+
native.symbols.setNextWebviewFlags(startTransparent, startPassthrough);
|
|
974
989
|
const webviewPtr = native.symbols.initWebview(
|
|
975
990
|
id,
|
|
976
991
|
windowPtr,
|
|
@@ -2103,6 +2118,8 @@ type WebviewTagInitParams = {
|
|
|
2103
2118
|
windowId: number;
|
|
2104
2119
|
navigationRules: string | null;
|
|
2105
2120
|
sandbox: boolean;
|
|
2121
|
+
transparent: boolean;
|
|
2122
|
+
passthrough: boolean;
|
|
2106
2123
|
};
|
|
2107
2124
|
|
|
2108
2125
|
export const internalRpcHandlers = {
|
|
@@ -2119,6 +2136,8 @@ export const internalRpcHandlers = {
|
|
|
2119
2136
|
frame,
|
|
2120
2137
|
navigationRules,
|
|
2121
2138
|
sandbox,
|
|
2139
|
+
transparent,
|
|
2140
|
+
passthrough,
|
|
2122
2141
|
} = params;
|
|
2123
2142
|
|
|
2124
2143
|
const url = !params.url && !html ? "https://electrobun.dev" : params.url;
|
|
@@ -2135,6 +2154,8 @@ export const internalRpcHandlers = {
|
|
|
2135
2154
|
renderer, //: "cef",
|
|
2136
2155
|
navigationRules,
|
|
2137
2156
|
sandbox,
|
|
2157
|
+
startTransparent: transparent,
|
|
2158
|
+
startPassthrough: passthrough,
|
|
2138
2159
|
});
|
|
2139
2160
|
|
|
2140
2161
|
return webviewForTag.id;
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -1087,7 +1087,6 @@ const defaultConfig = {
|
|
|
1087
1087
|
build: {
|
|
1088
1088
|
buildFolder: "build",
|
|
1089
1089
|
artifactFolder: "artifacts",
|
|
1090
|
-
targets: undefined as unknown, // Will default to current platform if not specified
|
|
1091
1090
|
useAsar: false,
|
|
1092
1091
|
asarUnpack: undefined as string[] | undefined, // Glob patterns for files to exclude from ASAR (e.g., ["*.node", "*.dll"])
|
|
1093
1092
|
cefVersion: undefined as string | undefined, // Override CEF version: "CEF_VERSION+chromium-CHROMIUM_VERSION"
|