JJ_MKVMaker/packages/webview_cef/lib/src/webview_manager.dart

222 lines
8.0 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:webview_cef/src/webview_inject_user_script.dart';
import 'webview.dart';
class WebviewManager extends ValueNotifier<bool> {
static final WebviewManager _instance = WebviewManager._internal();
factory WebviewManager() => _instance;
late Completer<void> _creatingCompleter;
final MethodChannel pluginChannel = const MethodChannel("webview_cef");
final _webViews = <int, WebViewController>{};
final _injectUserScripts = <int, InjectUserScripts?>{};
final _tempWebViews = <int, WebViewController>{};
final _tempInjectUserScripts = <int, InjectUserScripts?>{};
int nextIndex = 1;
bool? _hasNativeKeySupport;
get ready => _creatingCompleter.future;
/// Returns true if the platform has native key event handling (e.g., GTK on desktop Linux).
/// When false, Dart-side key handling should be used (e.g., eLinux).
Future<bool> get hasNativeKeySupport async {
_hasNativeKeySupport ??= await pluginChannel.invokeMethod<bool>('hasNativeKeySupport') ?? false;
return _hasNativeKeySupport!;
}
WebViewController createWebView({
Widget? loading,
InjectUserScripts? injectUserScripts,
}) {
int browserIndex = nextIndex++;
final controller =
WebViewController(pluginChannel, browserIndex, loading: loading);
_tempWebViews[browserIndex] = controller;
_tempInjectUserScripts[browserIndex] = injectUserScripts;
return controller;
}
void removeWebView(int browserId) {
if (browserId > 0) {
_webViews.remove(browserId);
}
}
WebviewManager._internal() : super(false);
/// Initializes the CEF runtime.
///
/// [rootCachePath] is the directory CEF stores its user data in. Leaving it out makes CEF use
/// the platform default, where it also keeps the process singleton lock that lets a second
/// instance hand its request to the first one and exit. CEF only accepts an absolute path, a
/// relative one is resolved against the working directory of the process.
///
/// [proxyServer] (JJ_MKVMaker, JJ_PATCH.md 6): every connection goes through this proxy (e.g. `http://127.0.0.1:1234`).
/// [doNotDeElevate] (JJ_MKVMaker, JJ_PATCH.md 7): keep running when the process is elevated (Chromium otherwise relaunches de-elevated and exits).
Future<void> initialize({String? userAgent, String? rootCachePath, String? proxyServer, bool doNotDeElevate = false}) async {
_creatingCompleter = Completer<void>();
try {
final Map<String, String> settings = <String, String>{
if (userAgent != null && userAgent.isNotEmpty) 'userAgent': userAgent,
if (rootCachePath != null && rootCachePath.isNotEmpty) 'rootCachePath': rootCachePath,
if (proxyServer != null && proxyServer.isNotEmpty) 'proxyServer': proxyServer,
if (doNotDeElevate) 'doNotDeElevate': '1',
};
if (settings.isNotEmpty) {
await pluginChannel.invokeMethod('init', settings);
} else {
await pluginChannel.invokeMethod('init');
}
pluginChannel.setMethodCallHandler(methodCallhandler);
// Wait for the platform to complete initialization.
await Future.delayed(const Duration(milliseconds: 300));
_creatingCompleter.complete();
value = true;
} on PlatformException catch (e) {
_creatingCompleter.completeError(e);
}
return _creatingCompleter.future;
}
@override
Future<void> dispose() async {
super.dispose();
pluginChannel.setMethodCallHandler(null);
_webViews.clear();
}
void onBrowserCreated(int browserIndex, int browserId) {
_webViews[browserId] = _tempWebViews[browserIndex]!;
_injectUserScripts[browserId] = _tempInjectUserScripts[browserIndex];
_tempWebViews.remove(browserIndex);
_tempInjectUserScripts.remove(browserIndex);
}
Future<void> methodCallhandler(MethodCall call) async {
switch (call.method) {
case "urlChanged":
int browserId = call.arguments["browserId"] as int;
_webViews[browserId]
?.listener
?.onUrlChanged
?.call(call.arguments["url"] as String);
return;
case "titleChanged":
int browserId = call.arguments["browserId"] as int;
_webViews[browserId]
?.listener
?.onTitleChanged
?.call(call.arguments["title"] as String);
return;
case "onConsoleMessage":
int browserId = call.arguments["browserId"] as int;
_webViews[browserId]?.listener?.onConsoleMessage?.call(
call.arguments["level"] as int,
call.arguments["message"] as String,
call.arguments["source"] as String,
call.arguments["line"] as int);
return;
case 'javascriptChannelMessage':
int browserId = call.arguments['browserId'] as int;
_webViews[browserId]?.onJavascriptChannelMessage?.call(
call.arguments['channel'] as String,
call.arguments['message'] as String,
call.arguments['callbackId'] as String,
call.arguments['frameId'] as String);
return;
case 'onTooltip':
int browserId = call.arguments['browserId'] as int;
_webViews[browserId]?.onToolTip?.call(call.arguments['text'] as String);
return;
case 'onCursorChanged':
int browserId = call.arguments['browserId'] as int;
_webViews[browserId]
?.onCursorChanged
?.call(call.arguments['type'] as int);
return;
case 'onFocusedNodeChangeMessage':
int browserId = call.arguments['browserId'] as int;
bool editable = call.arguments['editable'] as bool;
_webViews[browserId]?.onFocusedNodeChangeMessage(editable);
return;
case 'onImeCompositionRangeChangedMessage':
int browserId = call.arguments['browserId'] as int;
_webViews[browserId]?.onImeCompositionRangeChangedMessage?.call(
call.arguments['x'] as int,
call.arguments['y'] as int,
call.arguments['height'] as int);
return;
case 'onLoadStart':
int browserId = call.arguments["browserId"] as int;
String urlId = call.arguments["urlId"] as String;
await _injectUserScriptIfNeeds(browserId, _injectUserScripts[browserId]?.retrieveLoadStartInjectScripts() ?? []);
WebViewController controller =
_webViews[browserId] as WebViewController;
_webViews[browserId]?.listener?.onLoadStart?.call(controller, urlId);
return;
case 'onLoadEnd':
int browserId = call.arguments["browserId"] as int;
String urlId = call.arguments["urlId"] as String;
await _injectUserScriptIfNeeds(browserId, _injectUserScripts[browserId]?.retrieveLoadEndInjectScripts() ?? []);
WebViewController controller =
_webViews[browserId] as WebViewController;
_webViews[browserId]?.listener?.onLoadEnd?.call(controller, urlId);
return;
default:
}
}
Future<void> _injectUserScriptIfNeeds(int browserId, List<UserScript> scripts) async {
if (scripts.isEmpty) return;
await _webViews[browserId]?.ready;
for (final script in scripts) {
await _webViews[browserId]?.executeJavaScript(script.script);
}
}
Future<void> setCookie(String domain, String key, String val) async {
assert(value);
return pluginChannel.invokeMethod('setCookie', [domain, key, val]);
}
Future<void> deleteCookie(String domain, String key) async {
assert(value);
return pluginChannel.invokeMethod('deleteCookie', [domain, key]);
}
Future<dynamic> visitAllCookies() async {
assert(value);
return pluginChannel.invokeMethod('visitAllCookies');
}
Future<dynamic> visitUrlCookies(String domain, bool isHttpOnly) async {
assert(value);
return pluginChannel.invokeMethod('visitUrlCookies', [domain, isHttpOnly]);
}
Future<void> quit() async {
//only call this method when you want to quit the app
assert(value);
return pluginChannel.invokeMethod('quit');
}
}