flutter_linux_webview

A Linux Desktop implementation for the webview_flutter (v3.0.4) plugin, powered by CEF (Chromium Embedded Framework).

webview_flutter is a federated package, consisting of an app-facing package, platform interface package, and platform implementation packages.

This plugin package provides the Linux implementation for webview_flutter (v3.0.4) using CEF.

Depending on the architecture, the following CEF binary distribution provided at https://cef-builds.spotifycdn.com/index.html is downloaded in the source directory of the plugin at the first build time:

webview example main image multiple webviews example image

Supported Platforms

We have confirmed that this plugin works on some platforms but hangs or crashes on others.

See validation_report.md for details on how the plugin was validated on different platforms.

As the report results show, there are stability issues:

  • WebView creation appears to be somewhat unstable.
  • Using Flutter 3.16.3 (latest as of 2023-12-13), the plugin test hangs on all platforms.

We should fix these issues.
Update: We have recognized that the cause of these issues is likely accessing the same GL Context from multiple threads.

Run the example project

Go to example/.

Usage

1. Depend on it

Add flutter_linux_webview:^0.1.0 and webview_flutter:^3.0.4 as dependencies in your pubspec.yaml file.

Run these commands:

 $ flutter pub add flutter_linux_webview:'^0.1.0'
 $ flutter pub add webview_flutter:'^3.0.4'

This will add lines like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  webview_flutter: ^3.0.4
  flutter_linux_webview: ^0.1.0

2. Modify linux/CMakeLists.txt of your application

You need to add the following command to linux/CMakeLists.txt in your app:

include(flutter/ephemeral/.plugin_symlinks/flutter_linux_webview/linux/cmake/link_to_cef_library.cmake)

but must be placed after the following string:

add_executable(${BINARY_NAME})

The plugin will hang if above configuration is not added.

3. Import and Setup

Now in your Dart code, import these:

import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_linux_webview/flutter_linux_webview.dart';

Before creating the first WebView, you must call LinuxWebViewPlugin.initialize.

You must also set "WebView.platform = LinuxWebView();" to configure WebView to use the Linux implementation.

After that, you can use a WebView widget.

In Flutter 3.10 or later (as of Flutter 3.13), you must call LinuxWebViewPlugin.terminate when the application exits.
Prior to Flutter 3.10, you do not need to call LinuxWebViewPlugin.terminate because this plugin automatically exits.

See the example below.

Example

import 'dart:async';
// Required to use AppExitResponse for Fluter 3.10 or later
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_linux_webview/flutter_linux_webview.dart';

void main() {
  // ensureInitialized() is required if the plugin is initialized before runApp()
  WidgetsFlutterBinding.ensureInitialized();

  // Run `LinuxWebViewPlugin.initialize()` first before creating a WebView.
  LinuxWebViewPlugin.initialize(options: <String, String?>{
    'user-agent': 'UA String',
    'remote-debugging-port': '8888',
    'autoplay-policy': 'no-user-gesture-required',
  });

  // Configure [WebView] to use the [LinuxWebView].
  WebView.platform = LinuxWebView();

  runApp(const MaterialApp(home: _WebViewExample()));
}

class _WebViewExample extends StatefulWidget {
  const _WebViewExample({Key? key}) : super(key: key);

  @override
  _WebViewExampleState createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<_WebViewExample>
    with WidgetsBindingObserver {
  final Completer<WebViewController> _controller =
      Completer<WebViewController>();

  /// Prior to Flutter 3.10, comment out the following code since
  /// [WidgetsBindingObserver.didRequestAppExit] does not exist.
  // ===== begin: For Flutter 3.10 or later =====
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Future<AppExitResponse> didRequestAppExit() async {
    await LinuxWebViewPlugin.terminate();
    return AppExitResponse.exit;
  }
  // ===== end: For Flutter 3.10 or later =====

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('flutter_linux_webview example'),
      ),
      body: WebView(
        initialUrl: 'https://flutter.dev',
        initialCookies: const [
          WebViewCookie(name: 'mycookie', value: 'foo', domain: 'flutter.dev')
        ],
        onWebViewCreated: (WebViewController webViewController) {
          _controller.complete(webViewController);
        },
        javascriptMode: JavascriptMode.unrestricted,
      ),
      floatingActionButton: favoriteButton(),
    );
  }

  Widget favoriteButton() {
    return FutureBuilder<WebViewController>(
        future: _controller.future,
        builder: (BuildContext context,
            AsyncSnapshot<WebViewController> controller) {
          if (controller.hasData) {
            return FloatingActionButton(
              onPressed: () async {
                final String useragent = (await controller.data!
                    .runJavascriptReturningResult('navigator.userAgent'));
                final String title = (await controller.data!.getTitle())!;
                final String url = (await controller.data!.currentUrl())!;
                final String cookies = await (controller.data!
                    .runJavascriptReturningResult('document.cookie'));
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text(
                        'userAgent: $useragent, title: $title, url: $url, cookie: $cookies'),
                  ),
                );
              },
              child: const Icon(Icons.favorite),
            );
          }
          return Container();
        });
  }
}

The WebView behavior on Linux

On Linux, the behavior of some properties of WebView and some methods of WebViewController is different from that on Android/iOS due to limitations of the underlying browser or because some features have not yet been implemented.

Specification of properties of WebView on Linux

bool allowsInlineMediaPlayback

This only works on iOS.

On Linux, same as Android, this setting is ignored.

See WebView.allowsInlineMediaPlayback and WebSettings.allowsInlineMediaPlayback for the original description.

Color? backgroundColor

See WebView.backgroundColor and CreationParams.backgroundColor for the original description.

The color is used for the browser before a document is loaded and when no document color is specified. When the color is null the background is transparent. When the color is specified the alpha component must be either fully opaque (0xFF) or fully transparent (0x00). If the alpha component is fully opaque then the RGB components will be used as the background color. If the alpha component is fully transparent then transparent painting will be enabled.

bool? debuggingEnabled

This property is not supported due to CEF incompatibility* and is ignored on Linux.

Alternatively, --remote-debugging-port command line argument can be set at CEF startup using LinuxWebViewPlugin.initialize.

See WebView.debuggingEnabled and WebSettings.debuggingEnabled for the original description.

*CEF Incompatibility:

In Android webview_flutter, debuggingEnabled can be dynamically toggled enabled/disabled. However, in CEF, --remote-debugging-port cannot be changed later once CEF has started.

bool? gestureNavigationEnabled

This only works on iOS.

On Linux, same as Android, this setting is ignored.

See WebView.gestureNavigationEnabled and WebSettings.gestureNavigationEnabled for the original description.

Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers

This property is not implemented and it is not clear if it could be implemented.

See WebView.gestureRecognizers for the original description.

Note: This is not a WebView feature, but a PlatformViews feature. Also note that this plugin does not currently support touch devices.

List<WebViewCookie> initialCookies

See WebView.initialCookies and CreationParams.cookies.

AutoMediaPlaybackPolicy initialMediaPlaybackPolicy

This property is not supported due to CEF incompatibility* and is ignored on Linux.

Alternatively, --autoplay-policy=no-user-gesture-required command line argument can be set at CEF startup using LinuxWebViewPlugin.initialize.

See WebView.initialMediaPlaybackPolicy and CreationParams.autoMediaPlaybackPolicy for the original desciption.

*CEF Incompatibility:

In Android webview_flutter, initialMediaPlaybackPolicy is applied to each WebView at its creation. However, in CEF, --autoplay-policy=no-user-gesture-required applies to all browsers once CEF has started and cannot be changed later.

String? initialUrl

See WebView.initialUrl or CreationParams.initialUrl.

On Linux, when it is null the webview will be created with loading "about:blank".

Set<JavascriptChannel>? javascriptChannels

This property has not yet been implemented and is ignored on Linux.

TODO(Ino): implement javascriptChannels.

See WebView.javascriptChannels and CreationParams.javascriptChannelNames for the original description.

JavascriptMode? javascriptMode

This property has not yet been implemented and is ignored on Linux.

TODO(Ino): implement javascriptMode.

See WebView.javascriptMode and WebSettings.javascriptMode for the original description.

Note: This property cannot be fully supported due to CEF incompatibility.
In Android webview_flutter, javscriptMode can be changed dynamically.
However, in CEF, once a browser is created, it is not possible to change whether javascript is enabled or disabled unless the browser is recreated.

bool? navigationDelegate

This property has not yet been implemented and is ignored on Linux.

TODO(Ino): implement navigationDelegate.

See WebView.navigationDelegate and WebSettings.hasNavigationDelegate for the original description.

PageFinishedCallback? onPageFinished

See WebView.onPageFinished.

PageStartedCallback? onPageStarted

See WebView.onPageStarted.

bool? onProgress

See WebView.onProgress.

WebResourceErrorCallback? onWebResourceError

See WebView.onWebResourceError.

On Linux, currently only errorCode and description are supported. domain, failingUrl and errorType are not yet supported.

TODO(Ino): improve onWebResourceError support.

WebViewCreatedCallback? onWebViewCreated

This is the only interface to get WebViewController.

See WebView.onWebViewCreated

String? userAgent

This property is not supported due to CEF incompatibility* and is ignored on Linux.

Alternatively, --user-agent="UA string" command line argument can be set at CEF startup using LinuxWebViewPlugin.initialize.

See WebView.userAgent and CreationParams.userAgent for the original description.

*CEF Incompatibility:

In Android webview_flutter, userAgent can be set for each WebView and can be changed dynamically. However, in CEF, --user-agent applies to all browsers once CEF has started and cannot be changed later.

bool? zoomEnabled

This property is not supported due to CEF incompatibility* and is ignored on Linux.

Alternatively, --disable-pinch command line argument can be set at CEF startup using LinuxWebViewPlugin.initialize.

See WebView.zoomEnabled and WebSettings.zoomEnabled for the original description.

*CEF Incompatibility:

In Android webview_flutter, zoomEnabled can be set for each WebView and can be changed dynamically. However, in CEF, --disable-pinch applies to all browsers once CEF has started and cannot be changed later.

Note: This plugin does not currently support touch devices.

Specification of methods of WebViewController on Linux

The following methods are the same for Android/iOS.

  • Future<bool> canGoBack()
  • Future<void> goBack()
  • Future<bool> canGoForward()
  • Future<void> goForward()
  • Future<String?> currentUrl()
  • Future<String?> getTitle()
  • Future<void> reload()
  • Future<String> runJavascriptReturningResult(String javaScriptString)
  • Future<void> runJavascript(String javaScriptString)
  • Future<String> evaluateJavascript(String javascriptString)

Some methods are not implemented or behave differently from Android/iOS:

Future<void> loadUrl(String url, {Map<String, String>? headers})

See WebViewLinuxPlatformController.loadUrl for details.

Limitations on Linux

Requests with headers cannot be made for an origin different from the current page. You must first navigate to the request origin (scheme + domain) using some other mechanism (WebViewController.loadUrl without headers, link click, etc).

Known bug

The timing of when Future is resolved is different from that expected on Android/iOS (see the integration test). When loadUrl is resolved, the new URL is expected to be available in currentUrl, but the current implementation does not do so.

Future<void> loadRequest(WebViewRequest request)

See WebViewLinuxPlatformController.loadRequest for details.

Limitations on Linux

Requests cannot be made for an origin different from the current page. You must first navigate to the request origin (scheme + domain) using some other mechanism (WebViewController.loadUrl without headers, link click, etc).

Known bug (?)

The timing of when Future is resolved is different from Android/iOS. Immediately after this method is resolved, the new URL cannot yet be obtained with currentUrl.

Future<void> loadHtmlString(String html, {String? baseUrl})

See WebViewLinuxPlatformController.loadHtmlString for details.

Limitations on Linux

baseUrl is not supported because the underlying browser does not support baseUrl.

Known bug (?)

The timing of when Future is resolved is different from Android/iOS. Immediately after this method is resolved, the new URL cannot yet be obtained with currentUrl.

Future<void> loadFile(String absoluteFilePath)

See WebViewLinuxPlatformController.loadFile for details.

Known bug (?)

The timing of when Future is resolved is different from Android/iOS. Immediately after this method is resolved, the new URL cannot yet be obtained with currentUrl.

Future<void> loadFlutterAsset(String key)

See WebViewLinuxPlatformController.loadFlutterAsset for details.

Known bug (?)

The timing of when Future is resolved is different from Android/iOS. Immediately after this method is resolved, the new URL cannot yet be obtained with currentUrl.

Future<void> clearCache()

Not supported because the underlying browser does not support it.

Future<int> getScrollX()

Not implemented on Linux. Will be supported in the future.

Future<int> getScrollY()

Not implemented on Linux. Will be supported in the future.

Future<void> scrollBy(int x, int y)

Not implemented on Linux. Will be supported in the future.

Future<void> scrollTo(int x, int y)

Not implemented on Linux. Will be supported in the future.

TODO

  • Upgrade to webview_flutter v4 interface
    • and add tests
  • Investigate and fix the WebView creation stability issues.
  • Implement javascriptChannels
  • Implement navigationDelegate
  • Implement javascriptMode
  • Improve onWebResourceError support
  • Implement getScrollX, getScrollY, scrollBy, scrollTo()
  • Update the underlying browser version (currently CEF/Chromium 96.0.4664.110)

Contributing

Thank you for your interest in contributing.

However, currently, we are not accepting contributions, and we are unable to respond to pull requests.

License

flutter_linux_webview is licensed under the 3-Clause BSD License, see LICENSE.

Portions of flutter_linux_webview include third-party software, each of which is licensed under its respective license. See third_party_base/README.md for more information.