사용자 요청 (10/10): "Tor 브라우저를 현재 브라우저 옆에 추가 · 설정에서 컴포넌트처럼 추가 · DNS 주소를 강제로 내가 설정한 주소로, 기본 8.8.8.8" (보조 8.8.4.4 는 관리자 결정). DNS 강제 (환경 설정 > 네트워크 · DNS: 시스템 DNS (끄기) / 직접 지정 (기본) / DNS over HTTPS) - 앱 안 HTTP 프록시 (127.0.0.1, CONNECT + http) 가 정한 서버로 이름을 푼다 (UDP, 잘리면 TCP, DoH, TTL 동안 기억). 닿지 않으면 기기 DNS 로 몰래 바꾸지 않고 502 + 알림 [시스템 DNS 로 바꾸기]. - 따르는 것: 앱의 HttpClient (HttpOverrides: 업데이트 · 컴포넌트 · AI 모델 · WebDAV · 번역 · 자막), Edge (--proxy-server) · Chrome 엔진 (webview_cef 패치 6), Android 웹 뷰 (ProxyController), yt-dlp --proxy, aria2 --all-proxy (+ c-ares 가 있으면 --async-dns-server). - IP 주소 · 같은 집 안 이름 (점 없는 이름, *.local, Tailscale *.ts.net 등) 은 기기 DNS (내 판단: NAS · WebDAV 가 그대로 열리게). Tor 브라우저 (컴포넌트 'tor', 처음 꺼짐, 화면은 웹 브라우저 바로 뒤) - Windows: 공식 Tor Expert Bundle 15.0.24 (tor 0.4.9.13) 을 받아 SHA-256 확인 (e9dc6ccc…2b65) → 앱 데이터 components/tor. Android: 같은 판의 libTor.so 를 APK 에 (tool/fetch_tor_android.ps1). - tor 를 자식 프로세스로 (SOCKS · 제어 포트는 빈 포트, 쿠키 인증, __OwningControllerProcess), Tor 화면을 닫으면 끔. - 웹 뷰 → Tor 다리 (HTTP 프록시 → SOCKS5, 이름은 Tor 에 넘김). Tor 100% · 프록시 적용 · 전용 프로필이 모두 된 뒤에만 웹 뷰를 만든다. WebRTC 끔, 권한 거절, 검색은 DuckDuckGo. - Windows: 따로 WebView2 환경 (tor_webview, InPrivate). Android: 웹 뷰 프록시가 앱 전체라 Tor 화면이 열려 있는 동안 일반 브라우저도 Tor (안내 띠, 관리자 결정), androidx.webkit 프로필 'jj_tor' (flutter_inappwebview_android 패치 jjProfile). 기능이 없으면 화면을 열지 않음. - 상태 줄 (연결 중 % / 연결됨 / 실패 + 다시 시도), [새 경로] (NEWNYM), 처음 열 때 "공식 Tor Browser 아님" 안내. - 라이선스: TOR_LICENSES.txt (Tor BSD-3 · Libevent · OpenSSL · zlib · zstd), tor 실행 파일은 GPL 빌드 → GPL-3.0. 시험: dns_proxy_test (가짜 DNS · 프록시 · SOCKS 다리), tor_test (새는 것 막기 · 번들 해시 · torrc), integration_test/tor_dns_real_test (Windows 실제: 8.8.8.8 로 풀기, 닿지 않는 DNS 는 실패, check.torproject.org "Congratulations"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| common | ||
| lib | ||
| third | ||
| windows | ||
| .gitignore | ||
| .metadata | ||
| CHANGELOG.md | ||
| JJ_PATCH.md | ||
| LICENSE | ||
| README.md | ||
| README.zh-CN.md | ||
| analysis_options.yaml | ||
| pubspec.yaml | ||
README.md
WebView CEF
English · 简体中文
A Flutter desktop WebView backed by CEF (Chromium Embedded Framework). It renders a full Chromium browser off-screen and presents it inside a Flutter Texture, so the web content composes natively with the rest of your Flutter UI on Windows, macOS, and Linux.
Built on CEF 149 (Chromium 149).
Features
- 🌐 Full Chromium engine — modern web standards, WebGL, HTML5 video, and more.
- ⚡ GPU zero-copy rendering (Windows & macOS) — frames go straight from Chromium's GPU output to Flutter via a shared texture (a D3D11 texture on Windows, an IOSurface on macOS), with no per-frame CPU color-swizzle or CPU→GPU upload. Linux still uses the software path.
- 🎞️ Adaptive frame rate (Windows & macOS) — frame production is driven by the display's vblank (
IDXGIOutput::WaitForVBlankon Windows,CVDisplayLinkon macOS), so the webview tracks your monitor's real refresh rate (e.g. 120/144 Hz) instead of being capped at 60 fps. Static content stays idle. - ⌨️ Real-time CJK/IME input — native composition pipeline; Chinese/Japanese/Korean preedit appears live and commits correctly.
- 🔌 JavaScript bridge — call into Dart from JS and evaluate JS from Dart.
- 🍪 Cookie management — read, set, and delete cookies.
- 🪟 Multiple instances — run several independent webviews at once.
- 📜 User-script injection — inject JS/CSS at document start or end.
- 🛠️ DevTools, mouse & trackpad input, navigation, and load/title/url events.
Supported platforms
| Platform | Minimum version | Architectures |
|---|---|---|
| Windows | Windows 10 | x64 |
| macOS | macOS 12.0 | arm64, x86_64, or universal (arm64 + x86_64) |
| Linux | — | x64, arm64 |
| eLinux | — | x64, arm64 |
Requirements
- Flutter >= 3.27.0, Dart >= 3.6.0 (tested against the latest stable Flutter, 3.44.x).
- A C++20 toolchain for the native side (required by CEF 149) — recent MSVC / Clang / GCC.
Upgrading from ≤ 0.2.2
0.5.0 is a large upgrade (Flutter 3.44 + CEF 149) with breaking changes on every platform. If you are coming from an older release, do the following:
- Toolchain — upgrade to Flutter ≥ 3.27.0 / Dart ≥ 3.6.0 (was 2.5.0 / 2.17.1). The native build now requires C++20 (CEF 149); make sure your app doesn't force the plugin target to an older C++ standard.
- Removed Dart API —
WebviewCefPlatform,MethodChannelWebviewCef, andgetPlatformVersion()were removed (along with theplugin_platform_interfacedependency). They were never the intended API and have no replacement (getPlatformVersionreturned a demo value). Import onlypackage:webview_cef/webview_cef.dartand useWebviewManager/WebViewController. - Windows —
initCEFProcesseschanged signature. Updatewindows/runner/main.cpp: it now takes theHINSTANCEand returns a sub-process exit code that must be returned immediately, as the first statement inwWinMain(see the Windows install snippet below). The minimum OS is now Windows 10. - macOS — raise the deployment target to 12.0: set
platform :osx, '12.0'inmacos/Podfileand the Runner target's macOS Deployment Target in Xcode (CEF 149's framework is built for 12.0). To enable multi-process rendering, add the one-linepost_installhook tomacos/Podfile(see the macOS install section). Builds target the host architecture by default (arm64 or x86_64); setWEBVIEW_CEF_MACOS_ARCH=universalbeforepod installfor a universal (arm64 + x86_64) app.
Installation
Windows
-
Add the dependency:
flutter pub add webview_cef -
Edit
windows/runner/main.cpp. Because of Chromium's multi-process architecture and to route input/IME and method-channel calls onto the Flutter engine thread, two hooks are required:#include "webview_cef/webview_cef_plugin_c_api.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { // Start the CEF sub-processes. MUST be the first thing in wWinMain. int exit_code = initCEFProcesses(instance); if (exit_code >= 0) { return exit_code; } // ... existing runner setup ...In the message loop, forward messages to CEF (enables keyboard input and lets CEF post to the Flutter engine thread):
::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); handleWndProcForCEF(msg.hwnd, msg.message, msg.wParam, msg.lParam); }IME is wired up automatically by the plugin — no extra runner code needed.
On the first build, the official CEF Standard Distribution (~330 MB, from https://cef-builds.spotifycdn.com) is downloaded into third/cef and libcef_dll_wrapper is compiled from source, so the first build takes noticeably longer.
macOS
Requires macOS 12.0 or newer — CEF 149 ships a framework with a 12.0 deployment target, so your app's macOS deployment target must be ≥ 12.0 (set it in
macos/Podfile(platform :osx, '12.0') and the Runner target). Older targets fail to link cleanly.
-
Add the dependency:
flutter pub add webview_cef -
Enable multi-process (recommended) by adding the helper hook to your
macos/Podfile's existingpost_install:post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end # webview_cef: embed the CEF helper sub-process apps (multi-process). require File.expand_path( 'Flutter/ephemeral/.symlinks/plugins/webview_cef/macos/embed_cef_helpers.rb', __dir__) WebviewCEF.install_helper_phase(installer) endThen
pod install(run automatically byflutter run). This installs an "Embed CEF Helpers" build phase that clones the prebuilt helper into the five CEF sub-process.appbundles inside your app — no manual Xcode target needed. Without this hook the plugin still works but falls back to single-process (an unsupported Chromium mode: no crash isolation, V8 proxy resolver disabled, etc.).
macOS uses CocoaPods, which does not run the CMake download path. Instead the podspec's prepare_command runs macos/scripts/download_cef.sh on pod install, which mirrors the Windows/Linux flow: it downloads the official CEF Standard Distribution for the selected architecture (from https://cef-builds.spotifycdn.com, version pinned by CEF_VERSION in third/download.cmake), compiles libcef_dll_wrapper from source, lays the framework out as a versioned macOS bundle, and installs everything into the (git-ignored) macos/third/cef. The first pod install therefore takes noticeably longer; subsequent runs are a no-op once the pinned version is present.
Requirements: cmake (and ninja, otherwise make is used) must be on PATH to build the wrapper — brew install cmake ninja.
The wrapper is built
Debugby default to matchflutter run/flutter build macos --debug. For a release build setCEF_WRAPPER_BUILD_TYPE=Releasebeforepod install(debug and release builds need a wrapper compiled in the matching configuration —#if DCHECK_IS_ON()changes its ABI).
Choosing the architecture
WEBVIEW_CEF_MACOS_ARCH selects which slices are prepared. It is read by both the download script and the podspec, so the CEF binaries and the EXCLUDED_ARCHS of your app always agree.
| Value | Result |
|---|---|
host (default) |
the build machine's architecture |
arm64 |
Apple Silicon only |
x86_64 |
Intel only |
universal |
both, merged with lipo |
WEBVIEW_CEF_MACOS_ARCH=universal pod install
cd .. && flutter build macos --release
A universal build downloads both CEF distributions and compiles the wrapper twice, so it takes about twice as long and needs roughly 8 GB of free scratch space. The framework binary, the ANGLE/SwiftShader dylibs beside it, libcef_dll_wrapper.a, and the helper executable are merged with lipo, then each is checked for the expected slices. Chromium's V8 startup snapshot is architecture specific (v8_context_snapshot.<arch>.bin), so both are copied into the framework's Resources and the right one is picked at runtime; the "Embed CEF Helpers" build phase fails the build if the helper does not cover every architecture the app is being built for. The selected value is recorded in macos/third/cef/version.txt, so changing it re-prepares macos/third/cef on the next pod install.
Linux
flutter pub add webview_cef
CEF is downloaded automatically on the first build (x64 and arm64 supported). Make sure the usual Flutter Linux desktop toolchain is installed (clang, cmake, ninja-build, libgtk-3-dev, pkg-config).
Quick start
import 'package:flutter/material.dart';
import 'package:webview_cef/webview_cef.dart';
class MyWebView extends StatefulWidget {
const MyWebView({super.key});
@override
State<MyWebView> createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
_controller = WebviewManager().createWebView(
loading: const Center(child: CircularProgressIndicator()),
);
_init();
}
Future<void> _init() async {
await WebviewManager().initialize(); // call once for the whole app
_controller.setWebviewListener(WebviewEventsListener(
onUrlChanged: (url) => debugPrint('url => $url'),
onLoadEnd: (controller, url) => debugPrint('loaded => $url'),
));
await _controller.initialize('https://flutter.dev');
}
@override
void dispose() {
_controller.dispose();
WebviewManager().quit(); // only when tearing down the whole app
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _controller,
builder: (_, ready, __) =>
ready ? _controller.webviewWidget : _controller.loadingWidget,
);
}
}
A full-featured example (navigation bar, cookies, JS bridge, DevTools) lives in example/.
Usage
Lifecycle
await WebviewManager().initialize(userAgent: 'my-app/1.0'); // once per app
final controller = WebviewManager().createWebView(loading: const Text('…'));
await controller.initialize('https://example.com');
// …
controller.dispose();
WebviewManager().quit(); // on app shutdown
Navigation
controller.loadUrl('https://example.com');
controller.reload();
controller.goBack();
controller.goForward();
controller.openDevTools();
Events
controller.setWebviewListener(WebviewEventsListener(
onTitleChanged: (title) {},
onUrlChanged: (url) {},
onLoadStart: (controller, url) {},
onLoadEnd: (controller, url) {},
onConsoleMessage: (level, message, source, line) {},
));
JavaScript bridge
// Dart -> JS
controller.executeJavaScript("document.title = 'set from Dart'");
final result = await controller.evaluateJavascript("1 + 1"); // "2"
// JS -> Dart
controller.setJavaScriptChannels({
JavascriptChannel(
name: 'Print',
onMessageReceived: (msg) {
debugPrint(msg.message);
controller.sendJavaScriptChannelCallBack(
false, "{'code':'200'}", msg.callbackId, msg.frameId);
},
),
});
Cookies
await WebviewManager().setCookie('example.com', 'key', 'value');
await WebviewManager().deleteCookie('example.com', 'key');
final all = await WebviewManager().visitAllCookies();
final some = await WebviewManager().visitUrlCookies('example.com', false);
User-script injection
final scripts = InjectUserScripts()
..add(UserScript("console.log('at document start')", ScriptInjectTime.LOAD_START))
..add(UserScript("console.log('at document end')", ScriptInjectTime.LOAD_END));
final controller = WebviewManager().createWebView(injectUserScripts: scripts);
eLinux 🐧
For eLinux, this plugin supports Wayland and DRM-GBM backends using a decoupled architecture that avoids GTK/X11 dependencies.
Runtime Dependencies
Ensure the target eLinux system has the following libraries installed:
libnss3libnspr4libfontconfig1libasound2
CEF Binary Compatibility
- Sandbox: CEF's sandbox is disabled by default (
--no-sandbox) to avoid SUID permission issues common on embedded filesystems. - Architecture: While this project supports x64, ensure you have the correct CEF binaries for your target architecture (ARM64 support requires corresponding CEF builds).
Setup
- Ensure your eLinux toolchain is correctly configured.
- Use the
flutter-elinuxSDK to build your application. - The plugin will automatically use the
elinux/port which implements an efficient pixel buffer rendering pipeline.
TO RUN
cd example/
flutter-elinux pub get
flutter-elinux build elinux --release
./build/elinux/x64/release/bundle/webview_cef_example -b .
Windows build options
These CMake options can be set on the plugin target (defaults shown):
| Option | Default | Effect |
|---|---|---|
WEBVIEW_CEF_GPU_TEXTURE |
ON |
Zero-copy GPU rendering (CEF OnAcceleratedPaint → Flutter GPU surface texture). Set OFF to fall back to the software pixel-buffer path. |
WEBVIEW_CEF_USE_DEBUG_CEF |
OFF |
Link/bundle the CEF Debug binaries even in Debug builds. By default Debug builds use the Release CEF binaries, because CEF's Debug DCHECKs crash off-screen rendering during IME. Turn ON only to step into CEF itself. |
Updating CEF
The CEF/Chromium version is pinned in one place — CEF_VERSION in third/download.cmake. Bump it to update CEF on all three platforms: Windows and Linux download it automatically, and macOS reads the same CEF_VERSION (via macos/scripts/download_cef.sh, run by the podspec's prepare_command) and re-downloads on the next pod install — no manual placement needed.
Demo
Screenshots
| Windows | macOS | Linux |
|---|---|---|
![]() |
![]() |
|
![]() |
![]() |
Roadmap
- Windows / macOS / Linux support
- Multiple instances
- JavaScript bridge & cookie management
- IME support (Windows / macOS / Linux; tested with Chinese IMEs)
- Mouse & trackpad input
- DevTools
- GPU zero-copy rendering & adaptive frame rate (Windows & macOS)
- Easier macOS multi-process helper-bundle integration (one-line Podfile hook)
- Universal (arm64 + x86_64) macOS builds
- Tear-free GPU sync (keyed-mutex) on Windows
Pull requests are welcome. Every PR runs build + flutter analyze CI on Windows, macOS, and Linux.
Credits
Inspired by flutter_webview_windows.




