electrobun 1.18.4-beta.6 → 2.0.1-beta.13

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -170
  3. package/bin/electrobun.cjs +230 -153
  4. package/package.json +18 -49
  5. package/bun.lock +0 -119
  6. package/dist/api/browser/builtinrpcSchema.ts +0 -19
  7. package/dist/api/browser/global.d.ts +0 -36
  8. package/dist/api/browser/index.ts +0 -234
  9. package/dist/api/browser/webviewtag.ts +0 -88
  10. package/dist/api/browser/wgputag.ts +0 -48
  11. package/dist/api/bun/ElectrobunConfig.ts +0 -530
  12. package/dist/api/bun/__tests__/ffi-contract.test.ts +0 -105
  13. package/dist/api/bun/core/ApplicationMenu.ts +0 -70
  14. package/dist/api/bun/core/BrowserView.ts +0 -419
  15. package/dist/api/bun/core/BrowserWindow.ts +0 -400
  16. package/dist/api/bun/core/BuildConfig.ts +0 -71
  17. package/dist/api/bun/core/ContextMenu.ts +0 -75
  18. package/dist/api/bun/core/GpuWindow.ts +0 -289
  19. package/dist/api/bun/core/Paths.ts +0 -5
  20. package/dist/api/bun/core/Socket.ts +0 -22
  21. package/dist/api/bun/core/Tray.ts +0 -197
  22. package/dist/api/bun/core/Updater.ts +0 -1162
  23. package/dist/api/bun/core/Utils.ts +0 -487
  24. package/dist/api/bun/core/WGPUView.ts +0 -167
  25. package/dist/api/bun/core/menuRoles.ts +0 -181
  26. package/dist/api/bun/events/ApplicationEvents.ts +0 -22
  27. package/dist/api/bun/events/event.ts +0 -29
  28. package/dist/api/bun/events/eventEmitter.ts +0 -45
  29. package/dist/api/bun/events/trayEvents.ts +0 -11
  30. package/dist/api/bun/events/webviewEvents.ts +0 -39
  31. package/dist/api/bun/events/windowEvents.ts +0 -23
  32. package/dist/api/bun/index.ts +0 -298
  33. package/dist/api/bun/preload/.generated/compiled.ts +0 -8
  34. package/dist/api/bun/preload/build.ts +0 -65
  35. package/dist/api/bun/preload/dragRegions.ts +0 -41
  36. package/dist/api/bun/preload/encryption.ts +0 -86
  37. package/dist/api/bun/preload/events.ts +0 -171
  38. package/dist/api/bun/preload/globals.d.ts +0 -45
  39. package/dist/api/bun/preload/index-sandboxed.ts +0 -28
  40. package/dist/api/bun/preload/index.ts +0 -77
  41. package/dist/api/bun/preload/internalRpc.ts +0 -80
  42. package/dist/api/bun/preload/overlaySync.ts +0 -107
  43. package/dist/api/bun/preload/webviewTag.ts +0 -451
  44. package/dist/api/bun/preload/wgpuTag.ts +0 -246
  45. package/dist/api/bun/proc/linux.md +0 -43
  46. package/dist/api/bun/proc/native.ts +0 -3385
  47. package/dist/api/bun/webGPU.ts +0 -346
  48. package/dist/api/bun/webgpuAdapter.ts +0 -3011
  49. package/dist/api/shared/bun-version.ts +0 -3
  50. package/dist/api/shared/cef-version.ts +0 -5
  51. package/dist/api/shared/electrobun-version.ts +0 -2
  52. package/dist/api/shared/naming.test.ts +0 -327
  53. package/dist/api/shared/naming.ts +0 -188
  54. package/dist/api/shared/platform.ts +0 -48
  55. package/dist/api/shared/rpc.ts +0 -541
  56. package/dist/main.js +0 -168
  57. package/dist/preload-full.js +0 -913
  58. package/dist/preload-sandboxed.js +0 -111
  59. package/dist/zig-sdk/electrobun.zig +0 -1993
  60. package/src/cli/bun.lockb +0 -0
  61. package/src/cli/index.ts +0 -5689
  62. package/src/cli/package-lock.json +0 -81
  63. package/src/cli/package.json +0 -11
@@ -1,8 +0,0 @@
1
- // Auto-generated file. Do not edit directly.
2
- // Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
3
-
4
- // Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
5
- export const preloadScript = "(function(){// src/bun/preload/encryption.ts\nfunction base64ToUint8Array(base64) {\n return new Uint8Array(atob(base64).split(\"\").map((char) => char.charCodeAt(0)));\n}\nfunction 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}\nasync function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", rawKey, { name: \"AES-GCM\" }, true, [\"encrypt\", \"decrypt\"]);\n}\nasync 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\nvar pendingRequests = {};\nvar requestId = 0;\nvar isProcessingQueue = false;\nvar sendQueue = [];\nfunction 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}\nfunction send(type, payload) {\n sendQueue.push(JSON.stringify({ type: \"message\", id: type, payload }));\n processQueue();\n}\nfunction 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}\nfunction 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\nfunction isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n if (target.closest(\".electrobun-webkit-app-region-no-drag\") || target.closest('[style*=\"app-region\"][style*=\"no-drag\"]')) {\n return false;\n }\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}\nfunction 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/overlaySync.ts\nclass OverlaySyncController {\n element;\n options;\n lastRect = { x: 0, y: 0, width: 0, height: 0 };\n resizeObserver = null;\n positionLoop = null;\n resizeHandler = null;\n burstUntil = 0;\n constructor(element, options) {\n this.element = element;\n this.options = {\n onSync: options.onSync,\n getMasks: options.getMasks ?? (() => []),\n burstIntervalMs: options.burstIntervalMs ?? 50,\n baseIntervalMs: options.baseIntervalMs ?? 100,\n burstDurationMs: options.burstDurationMs ?? 500\n };\n }\n start() {\n this.resizeObserver = new ResizeObserver(() => this.sync());\n this.resizeObserver.observe(this.element);\n const loop = () => {\n this.sync();\n const now = performance.now();\n const interval = now < this.burstUntil ? this.options.burstIntervalMs : this.options.baseIntervalMs;\n this.positionLoop = setTimeout(loop, interval);\n };\n this.positionLoop = setTimeout(loop, this.options.baseIntervalMs);\n this.resizeHandler = () => this.sync(true);\n window.addEventListener(\"resize\", this.resizeHandler);\n }\n stop() {\n if (this.resizeObserver)\n this.resizeObserver.disconnect();\n if (this.positionLoop)\n clearTimeout(this.positionLoop);\n if (this.resizeHandler) {\n window.removeEventListener(\"resize\", this.resizeHandler);\n }\n this.resizeObserver = null;\n this.positionLoop = null;\n this.resizeHandler = null;\n }\n forceSync() {\n this.sync(true);\n }\n setLastRect(rect) {\n this.lastRect = rect;\n }\n sync(force = false) {\n const rect = this.element.getBoundingClientRect();\n const newRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n if (newRect.width === 0 && newRect.height === 0) {\n return;\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.burstUntil = performance.now() + this.options.burstDurationMs;\n this.lastRect = newRect;\n const masks = this.options.getMasks();\n this.options.onSync(newRect, JSON.stringify(masks));\n }\n}\n\n// src/bun/preload/webviewTag.ts\nvar webviewRegistry = {};\n\nclass ElectrobunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n static get observedAttributes() {\n return [\"src\", \"html\"];\n }\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (newValue === null)\n return;\n if (this.webviewId === null)\n return;\n if (name === \"src\")\n this.loadURL(newValue);\n else if (name === \"html\")\n this.loadHTML(newValue);\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n getInitialNavigationRules() {\n const rawRules = this.getAttribute(\"navigation-rules\");\n if (rawRules === null) {\n return null;\n }\n const trimmed = rawRules.trim();\n if (!trimmed) {\n return [];\n }\n try {\n const parsed = JSON.parse(trimmed);\n if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === \"string\")) {\n throw new Error(\"navigation-rules must be a JSON string array\");\n }\n return parsed;\n } catch (error) {\n console.error(\"Invalid navigation-rules attribute:\", error);\n return [];\n }\n }\n async initWebview() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\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 navigationRules = this.getInitialNavigationRules();\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 webviewInitParams = {\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 sandbox,\n transparent,\n passthrough,\n ...navigationRules === null ? {} : { navigationRules }\n };\n const webviewId = await request(\"webviewTagInit\", webviewInitParams);\n this.webviewId = webviewId;\n this.id = `electrobun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers(initialRect);\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(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\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 return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.webviewId === null)\n return;\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\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 executeJavascript(js) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagExecuteJavascript\", { id: this.webviewId, js });\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 } 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 } 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}\nfunction 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/wgpuTag.ts\nvar wgpuTagRegistry = {};\n\nclass ElectrobunWgpuTag extends HTMLElement {\n wgpuViewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWgpuView());\n }\n disconnectedCallback() {\n if (this.wgpuViewId !== null) {\n send(\"wgpuTagRemove\", { id: this.wgpuViewId });\n delete wgpuTagRegistry[this.wgpuViewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n async initWgpuView() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n const hidden = this.hasAttribute(\"hidden\");\n const masks = this.getAttribute(\"masks\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n this.hidden = hidden;\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n try {\n const wgpuViewId = await request(\"wgpuTagInit\", {\n windowId: window.__electrobunWindowId,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n transparent,\n passthrough\n });\n this.wgpuViewId = wgpuViewId;\n this.id = `electrobun-wgpu-${wgpuViewId}`;\n wgpuTagRegistry[wgpuViewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n if (hidden) {\n this.toggleHidden(true);\n }\n requestAnimationFrame(() => {\n Object.values(wgpuTagRegistry).forEach((view) => {\n if (view !== this && view.wgpuViewId !== null) {\n view.syncDimensions(true);\n }\n });\n });\n this.emit(\"ready\", { id: wgpuViewId });\n } catch (err) {\n console.error(\"Failed to init WGPU view:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\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 return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagResize\", {\n id: this.wgpuViewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n toggleTransparent(value) {\n if (this.wgpuViewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"wgpuTagSetTransparent\", {\n id: this.wgpuViewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.wgpuViewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"wgpuTagSetPassthrough\", {\n id: this.wgpuViewId,\n passthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.wgpuViewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"wgpuTagSetHidden\", { id: this.wgpuViewId, hidden: this.hidden });\n }\n runTest() {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagRunTest\", { id: this.wgpuViewId });\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 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}\nfunction initWgpuTag() {\n if (!customElements.get(\"electrobun-wgpu\")) {\n customElements.define(\"electrobun-wgpu\", ElectrobunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-wgpu {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #000;\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\nfunction 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}\nfunction 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}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction 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}\nfunction 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}\nfunction 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\ninitEncryption().catch((err) => console.error(\"Failed to initialize encryption:\", err));\nvar internalMessageHandler = (msg) => {\n handleResponse(msg);\n};\nvar defaultUserMessageHandler = (msg) => {\n if (!window.__electrobunPendingHostMessages) {\n window.__electrobunPendingHostMessages = [];\n }\n window.__electrobunPendingHostMessages.push(msg);\n};\nif (!window.__electrobun) {\n window.__electrobun = {\n receiveInternalMessageFromHost: internalMessageHandler,\n receiveMessageFromHost: defaultUserMessageHandler,\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: defaultUserMessageHandler\n };\n} else {\n window.__electrobun.receiveInternalMessageFromHost = internalMessageHandler;\n window.__electrobun.receiveMessageFromHost = defaultUserMessageHandler;\n window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__electrobun.receiveMessageFromBun = defaultUserMessageHandler;\n}\nwindow.__electrobunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n};\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\ninitDragRegions();\ninitWebviewTag();\ninitWgpuTag();\n})();";
6
-
7
- // Minimal preload for sandboxed/untrusted webviews (lifecycle events only, no RPC)
8
- export const preloadScriptSandboxed = "(function(){// src/bun/preload/events.ts\nfunction 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}\nfunction 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}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction 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}\nfunction 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}\nfunction 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\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\n})();";
@@ -1,65 +0,0 @@
1
- // Standalone build script for the preload.
2
- // Normally this is run as part of "bun build.ts", but you can run this directly:
3
- // bun src/bun/preload/build.ts
4
-
5
- import { join, dirname } from "path";
6
- import { writeFileSync, mkdirSync } from "fs";
7
-
8
- async function buildPreload() {
9
- const preloadDir = dirname(import.meta.path);
10
- const outputDir = join(preloadDir, ".generated");
11
- const outputPath = join(outputDir, "compiled.ts");
12
-
13
- mkdirSync(outputDir, { recursive: true });
14
-
15
- // Build full preload (trusted webviews)
16
- const fullPreloadEntry = join(preloadDir, "index.ts");
17
- const fullResult = await Bun.build({
18
- entrypoints: [fullPreloadEntry],
19
- target: "browser",
20
- format: "esm",
21
- minify: false,
22
- });
23
-
24
- if (!fullResult.success) {
25
- console.error("Full preload build failed:", fullResult.logs);
26
- throw new Error("Failed to build full preload script");
27
- }
28
-
29
- // Build sandboxed preload (untrusted webviews)
30
- const sandboxedPreloadEntry = join(preloadDir, "index-sandboxed.ts");
31
- const sandboxedResult = await Bun.build({
32
- entrypoints: [sandboxedPreloadEntry],
33
- target: "browser",
34
- format: "esm",
35
- minify: false,
36
- });
37
-
38
- if (!sandboxedResult.success) {
39
- console.error("Sandboxed preload build failed:", sandboxedResult.logs);
40
- throw new Error("Failed to build sandboxed preload script");
41
- }
42
-
43
- // Bun does not currently support iife output, so we wrap the ESM bundle manually
44
- // to keep preload globals scoped for script injection.
45
- const fullPreloadJs = `(function(){${await fullResult.outputs[0]!.text()}})();`;
46
- const sandboxedPreloadJs = `(function(){${await sandboxedResult.outputs[0]!.text()}})();`;
47
-
48
- const outputContent = `// Auto-generated file. Do not edit directly.
49
- // Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
50
-
51
- // Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
52
- export const preloadScript = ${JSON.stringify(fullPreloadJs)};
53
-
54
- // Minimal preload for sandboxed/untrusted webviews (lifecycle events only, no RPC)
55
- export const preloadScriptSandboxed = ${JSON.stringify(sandboxedPreloadJs)};
56
- `;
57
-
58
- writeFileSync(outputPath, outputContent);
59
- console.log(`Preload scripts compiled to ${outputPath} (full + sandboxed)`);
60
- }
61
-
62
- buildPreload().catch((err) => {
63
- console.error("Failed to build preload:", err);
64
- process.exit(1);
65
- });
@@ -1,41 +0,0 @@
1
- // Drag Region Support for custom titlebars
2
- // Detects elements with CSS app-region: drag or .electrobun-webkit-app-region-drag class
3
-
4
- import "./globals.d.ts";
5
- import { send } from "./internalRpc";
6
-
7
- function isAppRegionDrag(e: MouseEvent): boolean {
8
- const target = e.target as HTMLElement;
9
- if (!target || !target.closest) return false;
10
-
11
- // If the target is inside a no-drag region, it should not trigger window move
12
- if (
13
- target.closest(".electrobun-webkit-app-region-no-drag") ||
14
- target.closest('[style*="app-region"][style*="no-drag"]')
15
- ) {
16
- return false;
17
- }
18
-
19
- // Check for inline style with app-region: drag
20
- const draggableByStyle = target.closest(
21
- '[style*="app-region"][style*="drag"]',
22
- );
23
- // Check for class-based drag region
24
- const draggableByClass = target.closest(".electrobun-webkit-app-region-drag");
25
-
26
- return !!(draggableByStyle || draggableByClass);
27
- }
28
-
29
- export function initDragRegions() {
30
- document.addEventListener("mousedown", (e) => {
31
- if (isAppRegionDrag(e)) {
32
- send("startWindowMove", { id: window.__electrobunWindowId });
33
- }
34
- });
35
-
36
- document.addEventListener("mouseup", (e) => {
37
- if (isAppRegionDrag(e)) {
38
- send("stopWindowMove", { id: window.__electrobunWindowId });
39
- }
40
- });
41
- }
@@ -1,86 +0,0 @@
1
- // Encryption/Decryption for secure RPC
2
- // Uses per-webview secret key set in window.__electrobunSecretKeyBytes
3
-
4
- import "./globals.d.ts";
5
-
6
- function base64ToUint8Array(base64: string): Uint8Array {
7
- return new Uint8Array(
8
- atob(base64)
9
- .split("")
10
- .map((char) => char.charCodeAt(0)),
11
- );
12
- }
13
-
14
- function uint8ArrayToBase64(uint8Array: Uint8Array): string {
15
- let binary = "";
16
- for (let i = 0; i < uint8Array.length; i++) {
17
- binary += String.fromCharCode(uint8Array[i]!);
18
- }
19
- return btoa(binary);
20
- }
21
-
22
- async function generateKeyFromBytes(rawKey: Uint8Array): Promise<CryptoKey> {
23
- return await window.crypto.subtle.importKey(
24
- "raw",
25
- rawKey as unknown as ArrayBuffer,
26
- { name: "AES-GCM" },
27
- true,
28
- ["encrypt", "decrypt"],
29
- );
30
- }
31
-
32
- export async function initEncryption(): Promise<void> {
33
- const secretKey = await generateKeyFromBytes(
34
- new Uint8Array(window.__electrobunSecretKeyBytes),
35
- );
36
-
37
- const encryptString = async (
38
- plaintext: string,
39
- ): Promise<{ encryptedData: string; iv: string; tag: string }> => {
40
- const encoder = new TextEncoder();
41
- const encodedText = encoder.encode(plaintext);
42
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
43
- const encryptedBuffer = await window.crypto.subtle.encrypt(
44
- { name: "AES-GCM", iv },
45
- secretKey,
46
- encodedText,
47
- );
48
-
49
- // Split the tag (last 16 bytes) from the ciphertext
50
- const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));
51
- const tag = new Uint8Array(encryptedBuffer.slice(-16));
52
-
53
- return {
54
- encryptedData: uint8ArrayToBase64(encryptedData),
55
- iv: uint8ArrayToBase64(iv),
56
- tag: uint8ArrayToBase64(tag),
57
- };
58
- };
59
-
60
- const decryptString = async (
61
- encryptedDataB64: string,
62
- ivB64: string,
63
- tagB64: string,
64
- ): Promise<string> => {
65
- const encryptedData = base64ToUint8Array(encryptedDataB64);
66
- const iv = base64ToUint8Array(ivB64);
67
- const tag = base64ToUint8Array(tagB64);
68
-
69
- // Combine encrypted data and tag to match the format expected by SubtleCrypto
70
- const combinedData = new Uint8Array(encryptedData.length + tag.length);
71
- combinedData.set(encryptedData);
72
- combinedData.set(tag, encryptedData.length);
73
-
74
- const decryptedBuffer = await window.crypto.subtle.decrypt(
75
- { name: "AES-GCM", iv: iv as unknown as ArrayBuffer },
76
- secretKey,
77
- combinedData as unknown as ArrayBuffer,
78
- );
79
-
80
- const decoder = new TextDecoder();
81
- return decoder.decode(decryptedBuffer);
82
- };
83
-
84
- window.__electrobun_encrypt = encryptString;
85
- window.__electrobun_decrypt = decryptString;
86
- }
@@ -1,171 +0,0 @@
1
- // Shared Event Emission for webview lifecycle events
2
- // Uses __electrobunEventBridge which is available on ALL webviews (including sandboxed)
3
- // Falls back to __electrobunInternalBridge for backwards compatibility until native code
4
- // is updated to include the eventBridge handler
5
- // This is a one-way channel for emitting events to native/bun - no RPC capability
6
-
7
- import "./globals.d.ts";
8
-
9
- // Emit a webview event to native code
10
- export function emitWebviewEvent(eventName: string, detail: string) {
11
- // setTimeout works around a race condition with Bun FFI
12
- setTimeout(() => {
13
- // Prefer eventBridge (available on all webviews), fall back to internalBridge
14
- // (for backwards compatibility until native code adds eventBridge handler)
15
- const bridge =
16
- window.__electrobunEventBridge || window.__electrobunInternalBridge;
17
- bridge?.postMessage(
18
- JSON.stringify({
19
- id: "webviewEvent",
20
- type: "message",
21
- payload: {
22
- id: window.__electrobunWebviewId,
23
- eventName,
24
- detail,
25
- },
26
- }),
27
- );
28
- });
29
- }
30
-
31
- // Set up standard lifecycle event listeners
32
- export function initLifecycleEvents() {
33
- // Emit dom-ready when page loads (top-level window only)
34
- window.addEventListener("load", () => {
35
- if (window === window.top) {
36
- emitWebviewEvent("dom-ready", document.location.href);
37
- }
38
- });
39
-
40
- // Track in-page navigation
41
- window.addEventListener("popstate", () => {
42
- emitWebviewEvent("did-navigate-in-page", window.location.href);
43
- });
44
-
45
- window.addEventListener("hashchange", () => {
46
- emitWebviewEvent("did-navigate-in-page", window.location.href);
47
- });
48
- }
49
-
50
- // Track cmd key state for SPA navigation detection
51
- let cmdKeyHeld = false;
52
- let cmdKeyTimestamp = 0;
53
- const CMD_KEY_THRESHOLD_MS = 500;
54
-
55
- export function isCmdHeld(): boolean {
56
- if (cmdKeyHeld) return true;
57
- return (
58
- Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0
59
- );
60
- }
61
-
62
- // Set up cmd+click detection for opening links in new windows
63
- export function initCmdClickHandling() {
64
- window.addEventListener(
65
- "keydown",
66
- (event) => {
67
- if (event.key === "Meta" || event.metaKey) {
68
- cmdKeyHeld = true;
69
- cmdKeyTimestamp = Date.now();
70
- }
71
- },
72
- true,
73
- );
74
-
75
- window.addEventListener(
76
- "keyup",
77
- (event) => {
78
- if (event.key === "Meta") {
79
- cmdKeyHeld = false;
80
- cmdKeyTimestamp = Date.now();
81
- }
82
- },
83
- true,
84
- );
85
-
86
- window.addEventListener("blur", () => {
87
- cmdKeyHeld = false;
88
- });
89
-
90
- // Intercept cmd+clicks on anchors before SPA frameworks can handle them
91
- window.addEventListener(
92
- "click",
93
- (event) => {
94
- if (event.metaKey || event.ctrlKey) {
95
- const anchor = (event.target as HTMLElement)?.closest?.("a");
96
- if (anchor && (anchor as HTMLAnchorElement).href) {
97
- event.preventDefault();
98
- event.stopPropagation();
99
- event.stopImmediatePropagation();
100
- emitWebviewEvent(
101
- "new-window-open",
102
- JSON.stringify({
103
- url: (anchor as HTMLAnchorElement).href,
104
- isCmdClick: true,
105
- isSPANavigation: false,
106
- }),
107
- );
108
- }
109
- }
110
- },
111
- true,
112
- );
113
- }
114
-
115
- // Intercept SPA navigation (history.pushState/replaceState) when cmd is held
116
- export function initSPANavigationInterception() {
117
- const originalPushState = history.pushState;
118
- const originalReplaceState = history.replaceState;
119
-
120
- history.pushState = function (
121
- state: unknown,
122
- title: string,
123
- url?: string | URL | null,
124
- ) {
125
- if (isCmdHeld() && url) {
126
- const resolvedUrl = new URL(String(url), window.location.href).href;
127
- emitWebviewEvent(
128
- "new-window-open",
129
- JSON.stringify({
130
- url: resolvedUrl,
131
- isCmdClick: true,
132
- isSPANavigation: true,
133
- }),
134
- );
135
- return;
136
- }
137
- return originalPushState.apply(this, [state, title, url]);
138
- };
139
-
140
- history.replaceState = function (
141
- state: unknown,
142
- title: string,
143
- url?: string | URL | null,
144
- ) {
145
- if (isCmdHeld() && url) {
146
- const resolvedUrl = new URL(String(url), window.location.href).href;
147
- emitWebviewEvent(
148
- "new-window-open",
149
- JSON.stringify({
150
- url: resolvedUrl,
151
- isCmdClick: true,
152
- isSPANavigation: true,
153
- }),
154
- );
155
- return;
156
- }
157
- return originalReplaceState.apply(this, [state, title, url]);
158
- };
159
- }
160
-
161
- // Prevent overscroll bounce effect
162
- export function initOverscrollPrevention() {
163
- document.addEventListener("DOMContentLoaded", () => {
164
- const style = document.createElement("style");
165
- style.type = "text/css";
166
- style.appendChild(
167
- document.createTextNode("html, body { overscroll-behavior: none; }"),
168
- );
169
- document.head.appendChild(style);
170
- });
171
- }
@@ -1,45 +0,0 @@
1
- // Type declarations for Electrobun preload globals
2
- // These are set dynamically per-webview before the preload script runs
3
-
4
- declare global {
5
- interface Window {
6
- __electrobunWebviewId: number;
7
- __electrobunWindowId: number;
8
- __electrobunRpcSocketPort: number;
9
- __electrobunHostSocketPort?: number;
10
- __electrobunSecretKeyBytes: number[];
11
- // Event-only bridge (all webviews, including sandboxed)
12
- __electrobunEventBridge?: {
13
- postMessage: (message: string) => void;
14
- };
15
- // Internal RPC bridge (trusted webviews only)
16
- __electrobunInternalBridge?: {
17
- postMessage: (message: string) => void;
18
- };
19
- // User RPC bridge (trusted webviews only)
20
- __electrobunHostBridge?: {
21
- postMessage: (message: string) => void;
22
- };
23
- __electrobunBunBridge?: {
24
- postMessage: (message: string) => void;
25
- };
26
- __electrobun_encrypt: (
27
- plaintext: string,
28
- ) => Promise<{ encryptedData: string; iv: string; tag: string }>;
29
- __electrobun_decrypt: (
30
- encryptedData: string,
31
- iv: string,
32
- tag: string,
33
- ) => Promise<string>;
34
- __electrobunSendToHost: (message: unknown) => void;
35
- __electrobunPendingHostMessages?: unknown[];
36
- __electrobun: {
37
- receiveMessageFromHost: (msg: unknown) => void;
38
- receiveInternalMessageFromHost: (msg: unknown) => void;
39
- receiveMessageFromBun: (msg: unknown) => void;
40
- receiveInternalMessageFromBun: (msg: unknown) => void;
41
- };
42
- }
43
- }
44
-
45
- export {};
@@ -1,28 +0,0 @@
1
- // Electrobun Sandboxed Preload Script (for untrusted webviews)
2
- // This is compiled to JS and injected into webviews that ARE sandboxed
3
- //
4
- // Minimal functionality for security: NO RPC, NO encryption, NO webview tags
5
- // Only includes: lifecycle events, cmd+click handling, overscroll prevention
6
- //
7
- // Before this script runs, the following must be set:
8
- // - window.__electrobunWebviewId
9
- // - window.__electrobunWindowId
10
- // - window.__electrobunEventBridge (event emission only)
11
-
12
- import "./globals.d.ts";
13
- import {
14
- initLifecycleEvents,
15
- initCmdClickHandling,
16
- initSPANavigationInterception,
17
- initOverscrollPrevention,
18
- } from "./events";
19
-
20
- // Initialize minimal features for sandboxed webviews
21
- // No RPC handlers - sandboxed webviews cannot communicate with Bun
22
- // No drag regions - sandboxed content shouldn't control window movement
23
- // No webview tags - sandboxed content cannot create OOPIFs
24
-
25
- initLifecycleEvents();
26
- initCmdClickHandling();
27
- initSPANavigationInterception();
28
- initOverscrollPrevention();
@@ -1,77 +0,0 @@
1
- // Electrobun Full Preload Script (for trusted webviews)
2
- // This is compiled to JS and injected into webviews that are NOT sandboxed
3
- //
4
- // Includes: RPC, encryption, drag regions, webview tags, lifecycle events
5
- //
6
- // Before this script runs, the following must be set:
7
- // - window.__electrobunWebviewId
8
- // - window.__electrobunWindowId
9
- // - window.__electrobunRpcSocketPort
10
- // - window.__electrobunHostSocketPort (optional alias)
11
- // - window.__electrobunSecretKeyBytes
12
- // - window.__electrobunEventBridge (event emission - all webviews)
13
- // - window.__electrobunInternalBridge (internal RPC - trusted only)
14
- // - window.__electrobunHostBridge (user RPC - trusted only)
15
- // - window.__electrobunBunBridge (legacy alias)
16
-
17
- import "./globals.d.ts";
18
- import { initEncryption } from "./encryption";
19
- import { handleResponse } from "./internalRpc";
20
- import { initDragRegions } from "./dragRegions";
21
- import { initWebviewTag } from "./webviewTag";
22
- import { initWgpuTag } from "./wgpuTag";
23
- import {
24
- emitWebviewEvent,
25
- initLifecycleEvents,
26
- initCmdClickHandling,
27
- initSPANavigationInterception,
28
- initOverscrollPrevention,
29
- } from "./events";
30
-
31
- // Initialize encryption first (async)
32
- initEncryption().catch((err) =>
33
- console.error("Failed to initialize encryption:", err),
34
- );
35
-
36
- // Set up global handlers for bun to call back
37
- // Wrapper to satisfy the (msg: unknown) => void type
38
- const internalMessageHandler = (msg: unknown) => {
39
- handleResponse(msg as { type: string; id: string; success: boolean; payload: unknown });
40
- };
41
-
42
- const defaultUserMessageHandler = (msg: unknown) => {
43
- // Buffer user RPC packets that arrive before the page-specific Electroview
44
- // instance installs the real handler.
45
- if (!window.__electrobunPendingHostMessages) {
46
- window.__electrobunPendingHostMessages = [];
47
- }
48
- window.__electrobunPendingHostMessages.push(msg);
49
- };
50
-
51
- if (!window.__electrobun) {
52
- window.__electrobun = {
53
- receiveInternalMessageFromHost: internalMessageHandler,
54
- receiveMessageFromHost: defaultUserMessageHandler,
55
- receiveInternalMessageFromBun: internalMessageHandler,
56
- receiveMessageFromBun: defaultUserMessageHandler,
57
- };
58
- } else {
59
- window.__electrobun.receiveInternalMessageFromHost = internalMessageHandler;
60
- window.__electrobun.receiveMessageFromHost = defaultUserMessageHandler;
61
- window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;
62
- window.__electrobun.receiveMessageFromBun = defaultUserMessageHandler;
63
- }
64
-
65
- // Allow preload scripts to send custom messages to the host webview
66
- window.__electrobunSendToHost = (message: unknown) => {
67
- emitWebviewEvent("host-message", JSON.stringify(message));
68
- };
69
-
70
- // Initialize all features
71
- initLifecycleEvents();
72
- initCmdClickHandling();
73
- initSPANavigationInterception();
74
- initOverscrollPrevention();
75
- initDragRegions();
76
- initWebviewTag();
77
- initWgpuTag();
@@ -1,80 +0,0 @@
1
- // Internal RPC System for webview tags, drag regions, etc.
2
- // Communicates with Bun via __electrobunInternalBridge
3
-
4
- import "./globals.d.ts";
5
-
6
- interface PendingRequest {
7
- resolve: (value: unknown) => void;
8
- reject: (reason: unknown) => void;
9
- }
10
-
11
- const pendingRequests: Record<string, PendingRequest> = {};
12
- let requestId = 0;
13
- let isProcessingQueue = false;
14
- const sendQueue: string[] = [];
15
-
16
- function processQueue() {
17
- if (isProcessingQueue) {
18
- setTimeout(processQueue);
19
- return;
20
- }
21
- if (sendQueue.length === 0) return;
22
-
23
- isProcessingQueue = true;
24
- const batch = JSON.stringify(sendQueue);
25
- sendQueue.length = 0;
26
- window.__electrobunInternalBridge?.postMessage(batch);
27
-
28
- // 2ms delay to work around Bun JSCallback threading issue
29
- setTimeout(() => {
30
- isProcessingQueue = false;
31
- }, 2);
32
- }
33
-
34
- export function send(type: string, payload: unknown) {
35
- // Format: { type: 'message', id: handlerName, payload: data }
36
- sendQueue.push(JSON.stringify({ type: "message", id: type, payload }));
37
- processQueue();
38
- }
39
-
40
- export function request(type: string, payload: unknown): Promise<unknown> {
41
- return new Promise((resolve, reject) => {
42
- const id = `req_${++requestId}_${Date.now()}`;
43
- pendingRequests[id] = { resolve, reject };
44
- // Format: { type: 'request', method: handlerName, id: requestId, params: data, hostWebviewId: ... }
45
- sendQueue.push(
46
- JSON.stringify({
47
- type: "request",
48
- method: type,
49
- id,
50
- params: payload,
51
- hostWebviewId: window.__electrobunWebviewId,
52
- }),
53
- );
54
- processQueue();
55
- // Timeout after 10s
56
- setTimeout(() => {
57
- if (pendingRequests[id]) {
58
- delete pendingRequests[id];
59
- reject(new Error(`Request timeout: ${type}`));
60
- }
61
- }, 10000);
62
- });
63
- }
64
-
65
- export function handleResponse(msg: {
66
- type: string;
67
- id: string;
68
- success: boolean;
69
- payload: unknown;
70
- }) {
71
- // msg format: { type: 'response', id: requestId, success: bool, payload: data }
72
- if (msg && msg.type === "response" && msg.id) {
73
- const pending = pendingRequests[msg.id];
74
- if (pending) {
75
- delete pendingRequests[msg.id];
76
- if (msg.success) pending.resolve(msg.payload);
77
- else pending.reject(msg.payload);
78
- }
79
- }
80
- }