Flutter WebView 内容拦截器 ーー AppWebView 6

原文 https://medium.com/@pichillilorenzo/webview-content-blockers-flutter-inappwebview-6-faba4d61c294

前言

在本文中,我们将了解如何使用 flutter_inappwebview 插件为我们的 WebView 实例创建自定义 Content Block。

内容拦截器通常用于拦截广告,但你也可以拦截任何其他内容。拦截行为包括隐藏元素、拦截负载,以及在 iOS 和 macOS 上从 WebView 请求中剥离 cookie。

请记住,一般来说,Content Blockers 不能实现与 AdBlock 或 AdBlock Plus 等专用 extension 相同级别的功能。Content Blockers 是一组规则,当 WebView 找到需要阻止的内容时,它将永远不会从 WebView 获得任何回调或通知。

通过 InAppWebViewSettings 类的 contentBlockers 属性,我们可以定义 WebView 将使用的 ContentBlockers 实例的列表。

正文

ContentBlocker 内容过滤类

ContentBlock 类是我们定义内容拦截行为的类。每个属性包含一个操作属性和一个触发器属性。该操作告诉 WebView 在遇到与触发器匹配的对象时要做什么。触发器告诉 WebView 何时执行相应的操作。

下面是一个基本的例子:

initialSettings: InAppWebViewSettings(contentBlockers: [

  ContentBlocker(

    trigger: ContentBlockerTrigger(

      urlFilter: ".*",

      resourceType: [

        ContentBlockerTriggerResourceType.IMAGE,

        ContentBlockerTriggerResourceType.STYLE_SHEET

      ]

    ),

    action: ContentBlockerAction(

      type: ContentBlockerActionType.BLOCK

    )

  )

]),

在这个例子中,我们说的是为每个 URL 阻止每个图像和样式表的加载。

将 triggers 触发器添加到 Content Blocker 内容阻止器

触发器必须定义所需的 urlFilter 属性,该属性将正则表达式指定为与 URL 匹配的字符串。其他属性是可选的,可以修改触发器的行为。例如,可以将触发器限制为特定域,或者在 WebView 找到与特定域匹配的触发器时不应用触发器。

下面是一个内容拦截器的例子,它带有一个针对图像和样式表资源的触发器,WebView 可以在任何域中找到这些资源,但是指定的域除外:

initialSettings: InAppWebViewSettings(contentBlockers: [

  ContentBlocker(

    trigger: ContentBlockerTrigger(

      urlFilter: ".*",

      resourceType: [

        ContentBlockerTriggerResourceType.IMAGE,

        ContentBlockerTriggerResourceType.STYLE_SHEET

      ],

      unlessDomain: ["example.com", "github.com", "pub.dev"]

    ),

    action: ContentBlockerAction(

      type: ContentBlockerActionType.BLOCK

    )

  )

]),

对于更深层次的触发器自定义,您可以使用 ContentBlockerTrigger 的其他属性:

  • 如果 URL 匹配应该区分大小写。默认情况下,它是不区分大小写的。
  • Resource ceType: “ ContentBlockerTriggerResourceType”的列表,表示规则应该匹配的资源类型(浏览器打算如何使用资源)。如果未指定,则该规则匹配所有资源类型。
  • IfDomain: 与 URL 域匹配的字符串列表; 将操作限制为特定域的列表。值必须是小写的 ASCII,或非 ASCII 的小写字母代码。在前面添加 * 以匹配域和子域。它不能和“ unless Domain”一起使用。
  • Unless 域名: 与 URL 域名匹配的字符串列表; 在提供列表中的域名以外的任何站点上执行操作。值必须是小写的 ASCII,或非 ASCII 的小写字母代码。在前面添加 * 以匹配域和子域。它不能与“ ifDomain”一起使用。
  • LoadType: “ ContentBlockerTriggerLoadType”的列表,可以包括两个相互排斥的值之一。如果未指定,则该规则匹配所有加载类型。“ ContentBlockerTriggerLoadType.FIRST_PARTY”仅在资源与主页资源具有相同的方案、域和端口时触发。如果资源与主页资源来自不同的域,则“ ContentBlockerTriggerLoadType.THIRD_PARTY”触发器。
  • IfTopUrl: 与整个主文档 URL 匹配的字符串列表; 将操作限制为 URL 模式的特定列表。值必须是小写的 ASCII,或非 ASCII 的小写字母代码。它不能和“ unless TopUrl”一起使用。
  • Unless TopUrl: 与整个主文档 URL 匹配的字符串数组; 在所提供列表中的 URL 模式以外的任何站点上进行操作。值必须是小写的 ASCII,或非 ASCII 的小写字母代码。它不能与“ ifTopUrl”一起使用。
  • LoadContext: 指定加载上下文的字符串数组。
  • IfFrameUrl: 用于匹配 iframe URL 的正则表达式列表。

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

向内容屏蔽器添加 actions 操作

当触发器与资源匹配时,WebView 计算所有触发器并按顺序执行操作。

将具有相似操作的规则组合在一起以提高性能。例如,首先指定阻止内容加载的规则,然后指定阻止 Cookie 的规则。

操作只有两个有效属性: type 和 selector。操作类型是必需的。 如果类型是 ContentBlockerActionType.CSS_DISPLAY_NONE,也需要一个选择器; 否则,选择器是可选的。

下面是一个简单的例子:

initialSettings: InAppWebViewSettings(contentBlockers: [

  ContentBlocker(

    trigger: ContentBlockerTrigger(

      urlFilter: "https://flutter.dev/.*",

    ),

    action: ContentBlockerAction(

      type: ContentBlockerActionType.CSS_DISPLAY_NONE,

      selector: '.notification, .media, #developer-story'

    )

  )

]),

有效类型包括:

  • BLOCK: 停止加载资源。如果资源被缓存,缓存将被忽略。
  • BLOCK_COOKIES: 在将 Cookie 发送到服务器之前,从头中剥离 Cookie。这只能阻止 Cookie,否则 WebView 的隐私政策是可以接受的。结合“ IGNORE_PREVIOUS_RULES”不会覆盖浏览器的隐私设置。
  • CSS_DISPLAY_NONE: 基于 CSS 选择器隐藏页面元素。选择器字段包含选择器列表。任何匹配的元素都将其 display 属性设置为 none,这将隐藏它。
  • MAKE_HTTPS: 将 URL 从 http 更改为 https。具有指定(非默认)端口的 URL 和使用其他协议的链接不受影响。
  • IGNORE_PREVIOUS_RULES: 忽略以前触发的动作。

检查每个特定类型的代码文档,以了解哪个平台支持它。

创建一个简单的广告屏蔽器

让我们用我们学到的知识创建一个简单的广告拦截器。

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';

import 'package:flutter_inappwebview/flutter_inappwebview.dart';


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);


  

  State<MyApp> createState() => _MyAppState();

}


class _MyAppState extends State<MyApp> {

  final GlobalKey webViewKey = GlobalKey();


  // list of Ad URL filters to be used to block ads loading.

  final adUrlFilters = [

    ".*.doubleclick.net/.*",

    ".*.ads.pubmatic.com/.*",

    ".*.googlesyndication.com/.*",

    ".*.google-analytics.com/.*",

    ".*.adservice.google.*/.*",

    ".*.adbrite.com/.*",

    ".*.exponential.com/.*",

    ".*.quantserve.com/.*",

    ".*.scorecardresearch.com/.*",

    ".*.zedo.com/.*",

    ".*.adsafeprotected.com/.*",

    ".*.teads.tv/.*",

    ".*.outbrain.com/.*"

  ];


  final List<ContentBlocker> contentBlockers = [];

  var contentBlockerEnabled = true;


  InAppWebViewController? webViewController;


  

  void initState() {

    super.initState();


    // for each Ad URL filter, add a Content Blocker to block its loading.

    for (final adUrlFilter in adUrlFilters) {

      contentBlockers.add(ContentBlocker(

          trigger: ContentBlockerTrigger(

            urlFilter: adUrlFilter,

          ),

          action: ContentBlockerAction(

            type: ContentBlockerActionType.BLOCK,

          )));

    }


    // apply the "display: none" style to some HTML elements

    contentBlockers.add(ContentBlocker(

        trigger: ContentBlockerTrigger(

          urlFilter: ".*",

        ),

        action: ContentBlockerAction(

            type: ContentBlockerActionType.CSS_DISPLAY_NONE,

            selector: ".banner, .banners, .ads, .ad, .advert")));

  }


  

  Widget build(BuildContext context) {

    return Scaffold(

        appBar: AppBar(

          title: const Text("Ads Content Blocker"),

          actions: [

            TextButton(

              onPressed: () async {

                contentBlockerEnabled = !contentBlockerEnabled;

                if (contentBlockerEnabled) {

                  await webViewController?.setSettings(

                      settings: InAppWebViewSettings(

                          contentBlockers: contentBlockers));

                } else {

                  await webViewController?.setSettings(

                      settings: InAppWebViewSettings(contentBlockers: []));

                }

                webViewController?.reload();


                setState(() {});

              },

              style: TextButton.styleFrom(foregroundColor: Colors.white),

              child: Text(contentBlockerEnabled ? 'Disable' : 'Enable'),

            )

          ],

        ),

        body: SafeArea(

            child: Column(children: <Widget>[

          Expanded(

            child: Stack(

              children: [

                InAppWebView(

                  key: webViewKey,

                  initialUrlRequest:

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

                  initialSettings:

                      InAppWebViewSettings(contentBlockers: contentBlockers),

                  onWebViewCreated: (controller) {

                    webViewController = controller;

                  },

                ),

              ],

            ),

          ),

        ])));

  }

}

使用这些规则可以防止出现大量的广告,比如谷歌广告。

单击禁用/启用按钮禁用或启用广告屏蔽功能。

WebView 广告屏蔽器示例。

代码

https://github.com/ducafecat/flutter_inappwebview_examples

结束语

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

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

祝你有一个美好的一天~

猫哥课程


© 猫哥

  • 微信 ducafecat

  • https://wiki.ducafecat.tech

  • https://ducafecat.com

Last Updated:
Contributors: ducafecat