{"id":50202937,"url":"https://github.com/webliteca/swingwebview","last_synced_at":"2026-05-26T00:00:50.842Z","repository":{"id":50439454,"uuid":"226397723","full_name":"webliteca/swingwebview","owner":"webliteca","description":"Java port of the Zserge Webview.  Tiny cross-platform WebView","archived":false,"fork":false,"pushed_at":"2026-05-24T22:42:07.000Z","size":6683,"stargazers_count":93,"open_issues_count":8,"forks_count":17,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-05-25T00:28:05.670Z","etag":null,"topics":["chromium","edge","java","ui","webkit","webview"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webliteca.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-12-06T19:43:39.000Z","updated_at":"2026-05-21T02:41:55.000Z","dependencies_parsed_at":"2022-08-12T21:20:53.976Z","dependency_job_id":null,"html_url":"https://github.com/webliteca/swingwebview","commit_stats":null,"previous_names":["webliteca/swingwebview"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/webliteca/swingwebview","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webliteca%2Fswingwebview","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webliteca%2Fswingwebview/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webliteca%2Fswingwebview/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webliteca%2Fswingwebview/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webliteca","download_url":"https://codeload.github.com/webliteca/swingwebview/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webliteca%2Fswingwebview/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33497930,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T14:31:05.219Z","status":"ssl_error","status_checked_at":"2026-05-25T14:31:02.878Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chromium","edge","java","ui","webkit","webview"],"created_at":"2026-05-26T00:00:30.271Z","updated_at":"2026-05-26T00:00:50.834Z","avatar_url":"https://github.com/webliteca.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WebView\n\nA cross-platform native WebView component for embedding in Java Swing\napplications.  Java port of the tiny, light-weight\n[WebView](https://github.com/zserge/webview) by\n[Serge Zaitsev](https://zserge.com).\n\n## Installation\n\nAdd the dependency to your `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eca.weblite\u003c/groupId\u003e\n    \u003cartifactId\u003ewebview\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nThe jar bundles the native libraries for macOS, Linux, and Windows — no\nadditional native install step is required, with one exception:\n\n* **Windows** requires the system-wide Microsoft Edge WebView2 Runtime,\n  which ships with current Windows 11 / Edge.  On older Windows, install\n  the Evergreen Runtime from\n  \u003chttps://developer.microsoft.com/microsoft-edge/webview2/\u003e.\n\n## Platform support\n\n| Platform | Heavyweight | Lightweight |\n|---|---|---|\n| **macOS** (Cocoa / WKWebView) | Full (rendering, input, resize, tab visibility) | Stub — falls back to default Swing background |\n| **Linux** (WebKitGTK / X11) | Rendering, mouse, scroll, resize, tab switching work.  Visible text-input feedback (caret blink, characters appearing as typed) is **unreliable** because of how GTK frame-clock and focus interact with `XReparentWindow` under a foreign (non-GTK) parent. | **Full** — rendering + mouse (click, drag, scroll, hover) + keyboard (typing, Backspace, Delete, arrows, function keys, common modifiers) |\n| **Windows** (WebView2) | Full (rendering, input, resize, tab visibility) on Windows 11 | Stub |\n\nThe `WebViewComponent.create()` factory picks the right mode for the\ncurrent platform (heavyweight on macOS / Windows, lightweight on\nLinux), so most callers don't need to think about it.\n\n### Clipboard \u0026 editing shortcuts\n\nThe standard platform shortcut (`Cmd` on macOS, `Ctrl` on Linux /\nWindows) + `C` / `V` / `X` / `A` performs Copy / Paste / Cut /\nSelect-All inside the embedded WebView on all platforms.  A\n`KeyEventDispatcher` installed on the component routes the shortcut to\nthe native editing primitive — `[WKWebView copy:/paste:/cut:/selectAll:]`\non macOS, `webkit_web_view_execute_editing_command` on Linux,\n`document.execCommand` on Windows.  Sibling Swing widgets (a\n`JTextField` in a toolbar above the WebView, etc.) keep their default\nshortcut handling — the dispatcher only fires when the user is actually\ninteracting with the WebView.\n\n## Quick start\n\n```java\nimport ca.weblite.webview.swing.WebViewComponent;\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class Demo {\n    public static void main(String[] args) {\n        SwingUtilities.invokeLater(() -\u003e {\n            WebViewComponent wv = WebViewComponent.create();\n            wv.setUrl(\"https://example.com\");\n            wv.setPreferredSize(new Dimension(900, 600));\n\n            JFrame frame = new JFrame(\"WebView Demo\");\n            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\n            frame.add(wv, BorderLayout.CENTER);\n            frame.pack();\n            frame.setVisible(true);\n        });\n    }\n}\n```\n\n## Choosing a mode\n\n`WebViewComponent.create()` returns whichever implementation is best\nfor the current platform.  Two concrete subclasses both extend\n`WebViewComponent`:\n\n* **`WebViewHeavyweightComponent`** — embeds the native WebView as a\n  child of the underlying heavyweight AWT peer.  Renders directly to\n  screen pixels.  Native compositing means the highest fidelity and\n  lowest overhead, but it interacts with Swing Z-order the way every\n  heavyweight AWT component does — it paints above any overlapping\n  lightweight Swing components in the same window (see \"Heavyweight\n  popup notes\" below).\n* **`WebViewLightweightComponent`** — renders the WebView into an\n  offscreen surface, ships the pixels to Java, and Swing paints them\n  into a regular `JComponent`.  Composites cleanly with arbitrary Swing\n  widgets and Z-order.  Higher per-frame cost than heavyweight; mouse\n  and keyboard input is forwarded from Swing.\n\nTo force a specific mode, either set the `ca.weblite.webview.mode`\nsystem property to `heavyweight` or `lightweight` (case-insensitive),\nor call the factory explicitly:\n\n```java\nimport ca.weblite.webview.swing.WebViewComponent;\nimport ca.weblite.webview.swing.WebViewComponent.Mode;\n\nWebViewComponent wv = WebViewComponent.create(Mode.HEAVYWEIGHT);\n// or Mode.LIGHTWEIGHT\n```\n\nYou can also instantiate `WebViewHeavyweightComponent` or\n`WebViewLightweightComponent` directly if you need to.\n\n### Heavyweight popup notes\n\nWhen using `WebViewHeavyweightComponent`, native Swing popups\n(`JComboBox` dropdowns, `JMenu`, tooltips) render *behind* the\nWebView's heavyweight peer unless you opt into heavyweight popup mode\nat app start:\n\n```java\nJPopupMenu.setDefaultLightWeightPopupEnabled(false);\nToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);\n```\n\nThis makes popups appear as real OS windows that sit above heavyweight\npeers.  Lightweight mode does not need this.\n\nThe embedded WebView does **not** take ownership of the host\napplication's event loop.\n\n### Lightweight notes\n\nThe lightweight component renders WebKit into a `GtkOffscreenWindow`,\nsnapshots `cairo_image_surface_t` pixels at ~30Hz into a\n`BufferedImage`, and paints that into the `JComponent` via\n`paintComponent`.  AWT `MouseEvent`s and `KeyEvent`s are translated to\n`GdkEvent`s and injected via `gtk_main_do_event`.  Notes:\n\n* The WebKitWebView's IM context is disabled because all input arrives\n  already-decoded from AWT.  This means CJK / IME composition is\n  **not** available in the lightweight component on Linux today.  Dead\n  keys and Compose key sequences (e.g. `é`, `ñ`) work for\n  ASCII-Latin-1 layouts but not for IME-driven layouts.\n* Right-click context menus and `\u003cselect\u003e` dropdowns from inside the\n  page log a `gdk_window_move_to_rect: assertion 'window-\u003etransient_for'`\n  warning and don't visibly appear — WebKit tries to position them\n  relative to a toplevel that doesn't exist in our offscreen model.\n  Not fatal; just a missing piece of UI for those interactions.\n* Heavyweight popup interop is *not* needed in lightweight mode —\n  Swing components like `JComboBox` and tooltips composite over the\n  WebView with their normal lightweight rendering.\n\n### Heavyweight platform notes\n\n* **Linux (GTK / WebKitGTK / X11)** — the WebView's GTK window is\n  reparented under the JAWT-managed X11 window via `XReparentWindow`.\n  A dedicated GTK pump thread drives the WebKitGTK main loop\n  independently of AWT's X11 event loop.  A 60Hz `g_timeout` drives\n  the paint pipeline (the X11 GdkFrameClock won't pace itself on a\n  reparented popup that has no WM relationship).  Requires\n  `libwebkit2gtk-4.0-dev` or `libwebkit2gtk-4.1-dev` plus `libxt-dev`\n  (JDK 8's `jawt_md.h` pulls in X11 Intrinsics).\n* **macOS (Cocoa / WKWebView)** — the WKWebView is added as a real\n  subview of `NSWindow.contentView` (looked up through the layer\n  hierarchy from the JAWT `windowLayer`), so WebKit's CARemoteLayer\n  compositing engages and input dispatch goes through AppKit's normal\n  responder chain.  All input works end-to-end.\n* **Windows (WebView2)** — a child `HWND` is created under the AWT\n  canvas HWND and an `ICoreWebView2Controller` + `ICoreWebView2` are\n  hosted inside it (modern stable WebView2 SDK).  Each embedded\n  WebView runs on its own worker thread that pumps a private message\n  queue.  `WebView2LoaderStatic.lib` is linked statically so we ship\n  just `webview.dll`, no separate `WebView2Loader.dll`.  The system\n  WebView2 Runtime (part of Edge / Windows 11) provides the actual\n  Chromium binaries.\n\n### Focus cooperation (macOS + Windows heavyweight)\n\nThe AWT focus chain and the native focus chain (AppKit responder /\nWin32 keyboard focus) are independent on these platforms, and the\nheavyweight WebView's native peer holds native focus in a way AWT\ndoesn't observe.  Two consequences are handled automatically:\n\n* When the user clicks into the WebView, the previously-focused Swing\n  `JTextComponent`'s caret is hidden (visual cue that typing now lands\n  in the WebView).  macOS hooks `becomeFirstResponder` on the\n  `WKWebView` via a runtime class swizzle; Windows hooks\n  `ICoreWebView2Controller::add_GotFocus`.\n* When the user clicks back to a Swing component in the same window,\n  the suppressed caret is restored and its blink timer is restarted\n  via a synthetic `FocusEvent.FOCUS_GAINED`.  On Windows we\n  additionally force Win32 keyboard focus back to the JFrame HWND\n  (cross-thread `SetFocus` via `AttachThreadInput`) so subsequent\n  keystrokes actually reach the Swing component — WebView2 otherwise\n  keeps Win32 focus on its child HWND and steals keystrokes.\n\nFor debugging, set `-Dca.weblite.webview.debugShortcut=true` (Java\nside) and `WEBVIEW_DEBUG_SHORTCUT=1` (native side) to log the\ndispatcher decisions and Win32 `SetFocus` calls.\n\n## Talking to JavaScript\n\nThree methods on `WebViewComponent` (and on the standalone `WebView`)\ncover the JS-interop surface:\n\n* **`eval(String js)`** — fire-and-forget.  Runs the snippet in the\n  current document; the return value is discarded.  Use for side\n  effects (`scrollTo`, `document.title = \"...\"`, click a hidden\n  button).\n* **`evalAsync(String js): CompletableFuture\u003cString\u003e`** — round-trips\n  the snippet's result back to Java.  The future resolves with the\n  `JSON.stringify`'d return value (`undefined` becomes `\"null\"`;\n  returned `Promise`s are awaited).  JS-side failures\n  (synchronous `throw`, Promise rejection, `JSON.stringify` `TypeError`)\n  complete the future exceptionally with a\n  `JavaScriptEvalException`.  The snippet runs inside an IIFE, so\n  **use `return` to yield a value** — a bare expression is not the\n  IIFE's return.\n* **`addJavascriptCallback(String name, JavascriptCallback cb)`** —\n  exposes a Java callback at `window.\u003cname\u003e(arg)` for the page to\n  call.  Use when the page initiates the conversation, or when a\n  long-lived JS subscription needs to push events to Java.\n\n```java\nWebViewComponent wv = WebViewComponent.create();\nwv.setUrl(\"https://example.com\");\n// ...add to JFrame and show...\n\n// Ask the page for its current scroll position once it loads.\nwv.evalAsync(\"return [window.scrollX, window.scrollY];\")\n  .thenAccept(json -\u003e System.out.println(\"scroll = \" + json));\n// Prints e.g. \"scroll = [0,240]\"\n\n// Await a Promise: the future resolves with the fetched body length.\nwv.evalAsync(\n    \"return fetch('/health').then(r =\u003e r.text()).then(t =\u003e t.length);\"\n).thenAccept(json -\u003e System.out.println(\"body length = \" + json));\n\n// JS error → future completes exceptionally.\nwv.evalAsync(\"return missing.value;\").exceptionally(t -\u003e {\n    Throwable cause = t.getCause();          // CompletionException wraps it\n    if (cause instanceof JavaScriptEvalException) {\n        System.err.println(\"page said no: \" + cause.getMessage());\n    }\n    return null;\n});\n```\n\n**Threading.**  On `WebViewComponent` (both heavyweight and lightweight)\nfuture continuations land on the Swing EDT, so a `.thenAccept(...)` can\ntouch Swing state directly.  On the standalone `WebView` continuations\nrun inline on the WebView's native UI thread — there's no Swing in the\nstandalone path; wrap with\n`.thenAcceptAsync(continuation, SwingUtilities::invokeLater)` if you\nneed EDT delivery there.\n\n**Lifecycle.**  Calling `evalAsync` before the component is displayed\n(or on the standalone `WebView` before `show()`, or after the window\ncloses) returns an already-failed future whose cause is an\n`IllegalStateException` — no native call is made.  See\n[`demos/WebViewAsyncEvalDemo/`](demos/WebViewAsyncEvalDemo/README.md)\nfor a runnable example.\n\n## Browser-initiated dialogs\n\nPages can call `window.alert`, `window.confirm`, `window.prompt`, and\nthey can include `\u003cinput type=\"file\"\u003e` elements whose click opens a\nfile picker.  `WebViewComponent.setDialogHandler` lets the host\napplication customise — or fully suppress — what shows up:\n\n```java\nwv.setDialogHandler(new WebViewDialogHandler() {\n    @Override public boolean confirmOpened(WebViewConfirmEvent e) {\n        return JOptionPane.showConfirmDialog(\n            frame, e.message(), \"Confirm\",\n            JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;\n    }\n});\n```\n\n* **Default behaviour.**  When no handler is installed (the initial\n  state), every dialog kind shows a Swing dialog — `JOptionPane` for\n  alert / confirm / prompt, `JFileChooser` for file picker — modal to\n  the host `JFrame` resolved via\n  `SwingUtilities.getWindowAncestor(component)`.  Override individual\n  methods to customise specific kinds; un-overridden methods fall\n  through to the Swing defaults.\n* **Drop mode for headless tests.**  Pass `null`:\n  `wv.setDialogHandler(null)` installs an internal drop handler that\n  returns the JS-spec cancel values synchronously without UI\n  (`alert` no-op, `confirm` → `false`, `prompt` → `null`, file\n  picker → empty list).  Required for unit tests in headless\n  environments.  To reset to the framework default, pass\n  `WebViewDialogHandler.DEFAULT` explicitly — `null` is NOT a reset.\n* **Threading.**  Handler methods run on the Swing EDT, marshaled\n  from whatever native thread fired the dialog.  Calling\n  `wv.evalAsync(js).get()` from inside a handler **deadlocks** (both\n  calls park on the EDT); use `.thenAccept(...)` instead, or\n  pre-compute the value before the dialog opens.\n* **Platform coverage (current).**  macOS heavyweight (WKWebView)\n  routes all four dialog kinds through the handler (STORY-004-001).\n  Linux WebKitGTK routes all four kinds through the handler in both\n  heavyweight and lightweight modes via the `script-dialog` and\n  `run-file-chooser` signals (STORY-004-002).  Windows WebView2\n  routes alert / confirm / prompt (and before-unload) through the\n  handler via the `ScriptDialogOpening` event combined with\n  `put_AreDefaultScriptDialogsEnabled(FALSE)` (STORY-004-003).  On\n  Windows, `\u003cinput type=\"file\"\u003e` continues to use the OS-native\n  Common Item Dialog — WebView2 exposes no public hook for the file\n  picker, so `filePickerOpened` never fires on Windows.  On Windows,\n  `frameUrl()` equals `pageUrl()` for now (top-level only) because\n  the `ScriptDialogOpening` event args do not expose a separate\n  frame URL.\n* **Linux file-picker `accept`-extension limitation.**  On Linux, the\n  `WebViewFilePickerEvent.acceptedExtensions` list is always empty\n  even when the page wrote `\u003cinput accept=\".png,.jpg\"\u003e` — WebKitGTK\n  exposes the extension filter as an opaque `GtkFileFilter` rather\n  than the original extension strings.  The page's MIME-type hints\n  (`accept=\"image/png\"` etc.) are surfaced via `acceptedMimeTypes`;\n  the page's own client-side `accept` validation continues to work.\n\nSee [`demos/WebViewDialogDemo/`](demos/WebViewDialogDemo/README.md)\nfor a runnable example that exercises all four dialog kinds in each\nof the three handler modes (default, custom, drop).\n\n## Demo\n\nSee [`demos/WebViewHeavyweightDemo/`](demos/WebViewHeavyweightDemo/README.md)\nfor a working example that exercises both heavyweight and lightweight\nmodes side-by-side, plus interaction with surrounding Swing widgets\n(JComboBox dropdowns, tab switching).  One-shot launcher scripts\n(`run-mac-demo.sh`, `run-linux-demo.sh`, `run-windows-demo.bat`) live\nat the project root.\n\nAdditional demos:\n\n* `demos/WebViewContextMenuDemo/` — exercises the right-click\n  context-menu API: target descriptor, link / image / editable /\n  selection cases, and the `setDefaultContextMenuEnabled` override.\n* `demos/WebViewAsyncEvalDemo/` — exercises `evalAsync(String)`:\n  primitive / object / Promise / `undefined` results, synchronous\n  throws and Promise rejections surfacing as\n  `JavaScriptEvalException`, concurrent in-flight calls, and EDT\n  delivery of continuations.\n* `demos/WebViewDialogDemo/` — exercises the new\n  `WebViewDialogHandler` API: default Swing dialogs\n  (`alert` / `confirm` / `prompt` / file picker), a custom handler\n  returning programmatic answers, and the\n  `setDialogHandler(null)` drop mode for headless tests.\n\n## Building from source\n\n```\ngit clone https://github.com/webliteca/swingwebview\ncd swingwebview\nmvn -DskipTests package\n```\n\nThis produces `target/webview-1.0-SNAPSHOT.jar`.  The build targets\nJava 8 bytecode (`maven.compiler.source` / `maven.compiler.target` =\n`1.8` in `pom.xml`); it works on JDK 8 and any newer LTS.  Pass\n`-Dmaven.compiler.release=8` if you want strict Java 8 API checking\nwhen building on JDK 9+.\n\n### Rebuilding native libs\n\nThe native libraries are **not** checked into git. Locally, you build\nthem for your own platform and they get bundled into your\n`target/*.jar`. For the Maven Central release, the\n`.github/workflows/maven-release.yml` workflow builds all 6\nplatform+arch combinations (`linux_64`, `linux_arm64`, `osx_64`,\n`osx_arm64`, `windows_64`, `windows_arm64`) on matching GitHub-hosted\nrunners and merges them into a single jar before publishing.\n\nTo build for your local platform:\n\n1. Run `build-mac.sh` / `build-linux.sh` / `build-windows.sh` on the\n   matching platform. These compile the native sources and drop the\n   binaries into `natives/\u003cplatform\u003e/`, which Maven then picks up as a\n   resource during `mvn package`. The `natives/` directory is\n   gitignored.\n2. Mac and Linux native sources are under `src_c/`. Windows native\n   sources are under `windows/`.\n3. On Windows you need Visual Studio installed (VS 2019 works; earlier\n   versions likely do too). The `build-windows.sh` script runs under\n   git bash.\n\nA locally-built jar will only contain the native lib for whichever\nplatform you ran the build on. The cross-platform fat jar comes only\nfrom the CI release.\n\n## License\n\nMIT\n\n## Credits\n\n1. This library by [Steve Hannah](https://sjhannah.com)\n2. Original webview library by [Serge Zaitsev](https://zserge.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebliteca%2Fswingwebview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebliteca%2Fswingwebview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebliteca%2Fswingwebview/lists"}