Skip to content

Latest commit

 

History

History
540 lines (340 loc) · 22.9 KB

README.md

File metadata and controls

540 lines (340 loc) · 22.9 KB

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

This only works on iOS.

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

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

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.

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.

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.

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.

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.

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:

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.

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].

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].

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].

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.

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

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.