注入 JavaScript 到 Flutter Web View 中

原文 https://betterprogramming.pub/webview-javascript-injection-with-user-scripts-flutter-inappwebview-6-46d9969353a4

前言

了解如何利用 Flutter 的 InAppWebView 6 插件

有时你可能想在加载网页之前或之后注入 JavaScript 代码,以便从网页中添加、替换或删除内容,或者更改某些网页逻辑。

在本文中,我们将了解什么是用户脚本,以及如何使用它们在 WebView 中的特定时间使用 flutter_inappwebview 插件注入自定义 JavaScript 代码。

正文

UserScript 类

UserScript 类与 WKUserScript 本机类等效。它表示一个 JavaScript 代码,WebView 将其注入到网页以及任何其他后续的导航网页中。

使用 User Scripts 而不是仅仅注入一些 JavaScript 代码(例如,evaluateJavascript 方法)有什么好处?

UserScript 允许您在加载网页的其他资源之前注入 JavaScript 代码,并将 injectionTime 属性设置为 UserScriptInjectionTime.AT_DOCUMENT_START。

对于每个 UserScript,您可以设置一个可选的 Content World,设置是否使用 forMainFrameOnly 属性(仅适用于 iOS/macOS)将脚本注入主框架,以及一组允许起源的匹配规则(仅适用于 Android)。

安卓的注意事项

不幸的是,在 Android 上,当使用 UserScriptInjectionTime.AT_DOCUMENT_START 时,如果不支持 WebViewFeature.DOCUMENT_START_SCRIPT,就不能保证 JavaScript 代码在加载其他资源之前已经被注入,因为相应的本地类/特性不存在,所以 InAppWebView 试图尽快注入那个 UserScript。

要向 WebView 添加 UserScript,可以使用 WebView.initialUserScripts 属性:

InAppWebView(

  initialUrlRequest: URLRequest(url: WebUri('https://flutter.dev')),

  initialUserScripts: UnmodifiableListView<UserScript>([

    UserScript(

        source: "var foo = 49;",

        injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),

    UserScript(

        source: "var bar = 2;",

        injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END),

  ]),

  onLoadStop: (controller, url) async {

    var result = await controller.evaluateJavascript(source: "foo + bar");

    print(result); // 51

  },

),

对于每个 UserScript,您可以设置以下属性:

  • GroupName: 脚本的组名。
  • Source: 脚本的源代码。
  • InjectionTime: 将脚本注入 WebView 的时间。它可以是 UserScriptInjectionTime.AT_DOCUMENT_START 或 UserScriptInjectionTime.AT_DOCUMENT_END。
  • ForMainFrameOnly: 一个布尔值,指示是否将脚本注入主框架。指定 true 只将脚本注入到主框架中,或者指定 false 将脚本注入到所有框架中。默认值为 true。
  • AllowedOriginRules: 允许起源的一组匹配规则。只能在 Android 上使用,并且只能在支持 WebViewFeature.DOCUMENT_START_SCRIPT 特性的情况下使用。
  • ContentWorld: 执行范围,用于评估脚本以防止不同脚本之间的冲突。

检查每个特定属性的代码文档,以了解哪个平台支持该特性。

要在运行时添加或删除 User Scripts,还可以使用相应的方法,如 InAppWebViewController.addUserScript、 InAppWebViewController.RemoveUserScript 等。

请注意,在运行时添加或删除用户脚本,在网页加载后,将不会生效,直到下一个网页加载。

此外,对于每个 UserScript,您可以定义一个 groupName,例如,可以使用 InAppWebViewController.RemoveUserScriptsByGroupName 方法删除一组用户脚本。

这是对向 WebView 添加基本用户脚本的一个简单概述。

但是,用户脚本可以做什么呢?

它可以做任何一个普通脚本在网页上可以做的事情,比如修改 HTML 文档结构,监听 onload 等事件,加载外部资源(图像,XMLHttpRequest,获取,等等。.).你也可以通过 Flutter/Dart 端进行回传(查看官方的 JavaScript 通信文档了解更多信息)。

让我们用一个例子把我们学到的东西付诸实践吧!

用户脚本示例

在这个例子中,我们加载了 3 个自定义用户脚本的 https://example.com 网页,以改变一些 CSS 样式,并使用 javascript 处理程序为 Flutter/Dart side 的双向通信添加一些逻辑。

import 'dart:async';

import 'dart:collection';

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';

import 'package:flutter_inappwebview/flutter_inappwebview.dart';


final userScript1 = UserScript(

    groupName: "myUserScripts",

    source: ""
"

window.addEventListener('load', function(event) {

  document.body.style.backgroundColor = 'blue';

  document.body.style.padding = '20px';

});

""",

    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START);


final userScript2 = UserScript(

    groupName: "myUserScripts",

    source: ""
"

var h1 = document.querySelector('h1');

h1.addEventListener('click', function(event) {

  window.flutter_inappwebview.callHandler('h1Click', h1.innerText);

});

""",

    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END);


final userScript3 = UserScript(

    groupName: "myUserScripts",

    source: "document.querySelector('h1').innerHTML = 'Custom Title';",

    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END);


Future main() async {

  WidgetsFlutterBinding.ensureInitialized();

  if (!kIsWeb &&

      kDebugMode &&

      defaultTargetPlatform == TargetPlatform.android) {

    await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);

  }

  runApp(const MaterialApp(home: MyApp()));

}


class MyApp extends StatefulWidget {

  const MyApp({Key? key}) : super(key: key);


  

  _MyAppState createState() => _MyAppState();

}


class _MyAppState extends State<MyApp> {

  final GlobalKey webViewKey = GlobalKey();


  InAppWebViewController? webViewController;


  void handleClick(int item) async {

    switch (item) {

      case 0:

        await webViewController?.removeAllUserScripts();

        break;

      case 1:

        await webViewController?.addUserScript(userScript: userScript1);

        break;

      case 2:

        await webViewController?.removeUserScript(userScript: userScript1);

        break;

      case 3:

        await webViewController?.addUserScript(userScript: userScript2);

        break;

      case 4:

        await webViewController?.removeUserScript(userScript: userScript2);

        break;

      case 5:

        await webViewController?.addUserScript(userScript: userScript3);

        break;

      case 6:

        await webViewController?.removeUserScript(userScript: userScript3);

        break;

    }

  }


  

  Widget build(BuildContext context) {

    return Scaffold(

        appBar: AppBar(

          title: const Text("User Scripts"),

          actions: <Widget>[

            IconButton(

                onPressed: () {

                  webViewController?.reload();

                },

                icon: const Icon(Icons.refresh)),

            PopupMenuButton<int>(

              onSelected: (item) => handleClick(item),

              itemBuilder: (context) => [

                const PopupMenuItem<int>(

                    value: 0, child: Text('Remove User Scripts')),

                const PopupMenuItem<int>(

                    value: 1, child: Text('Add User Script 1')),

                const PopupMenuItem<int>(

                    value: 2, child: Text('Remove User Script 1')),

                const PopupMenuItem<int>(

                    value: 3, child: Text('Add User Script 2')),

                const PopupMenuItem<int>(

                    value: 4, child: Text('Remove User Script 2')),

                const PopupMenuItem<int>(

                    value: 5, child: Text('Add User Script 3')),

                const PopupMenuItem<int>(

                    value: 6, child: Text('Remove User Script 3')),

              ],

            ),

          ],

        ),

        body: Column(children: <Widget>[

          Expanded(

            child: Stack(

              children: [

                InAppWebView(

                  key: webViewKey,

                  initialUrlRequest:

                      URLRequest(url: WebUri('https://example.com')),

                  initialUserScripts: UnmodifiableListView<UserScript>(

                      [userScript1, userScript2, userScript3]),

                  onWebViewCreated: (controller) {

                    webViewController = controller;

                    controller.addJavaScriptHandler(

                        handlerName: 'h1Click',

                        callback: (arguments) {

                          final String h1InnerText = arguments[0];

                          showDialog(

                            context: context,

                            builder: (context) {

                              return AlertDialog(

                                title: const Text('h1 clicked'),

                                content: Text(h1InnerText),

                              );

                            },

                          );

                        });

                  },

                ),

              ],

            ),

          ),

        ]));

  }

}

使用右上角的操作按钮删除或添加用户脚本,然后重新加载网页以使更改生效。

结果如下:

WebView User Scripts example.

WebView 用户脚本示例。

WebView 提供了强大的工具来操作 JavaScript,用户脚本特性就是其中之一。

结束语

如果本文对你有帮助,请转发让更多的朋友阅读。

也许这个操作只要你 3 秒钟,对我来说是一个激励,感谢。

祝你有一个美好的一天~

猫哥课程


© 猫哥

  • 微信 ducafecat

  • https://wiki.ducafecat.tech

  • https://ducafecat.com

Last Updated:
Contributors: ducafecat