import 'types/main.dart'; ///Class that represents a set of rules to use block content in the browser window. /// ///On iOS, it uses [WKContentRuleListStore](https://developer.apple.com/documentation/webkit/wkcontentruleliststore). ///On Android, it uses a custom implementation because such functionality doesn't exist. /// ///In general, this [article](https://developer.apple.com/documentation/safariservices/creating_a_content_blocker) can be used to get an overview about this functionality ///but on Android there are two types of [action] that are unavailable: `block-cookies` and `ignore-previous-rules`. class ContentBlocker { ///Trigger of the content blocker. The trigger tells to the WebView when to perform the corresponding action. ContentBlockerTrigger trigger; ///Action associated to the trigger. The action tells to the WebView what to do when the trigger is matched. ContentBlockerAction action; ContentBlocker({required this.trigger, required this.action}); Map> toMap() { return {"trigger": trigger.toMap(), "action": action.toMap()}; } static ContentBlocker fromMap(Map> map) { return ContentBlocker( trigger: ContentBlockerTrigger.fromMap( Map.from(map["trigger"]!)), action: ContentBlockerAction.fromMap( Map.from(map["action"]!))); } } ///Trigger of the content blocker. The trigger tells to the WebView when to perform the corresponding action. ///A trigger dictionary must include an [ContentBlockerTrigger.urlFilter], which specifies a pattern to match the URL against. ///The remaining properties are optional and modify the behavior of the trigger. ///For example, you can limit the trigger to specific domains or have it not apply when a match is found on a specific domain. class ContentBlockerTrigger { ///A regular expression pattern to match the URL against. String urlFilter; ///A list of regular expressions to match iframes URL against. /// ///*NOTE*: available only on iOS. List ifFrameUrl; ///A Boolean value. The default value is `false`. /// ///*NOTE*: available only on iOS. bool urlFilterIsCaseSensitive; ///A list of [ContentBlockerTriggerResourceType] representing the resource types (how the browser intends to use the resource) that the rule should match. ///If not specified, the rule matches all resource types. List resourceType; ///A list of strings matched to a URL's domain; limits action to a list of specific domains. ///Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. Can't be used with [ContentBlockerTrigger.unlessDomain]. List ifDomain; ///A list of strings matched to a URL's domain; acts on any site except domains in a provided list. ///Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. Can't be used with [ContentBlockerTrigger.ifDomain]. List unlessDomain; ///A list of [ContentBlockerTriggerLoadType] that can include one of two mutually exclusive values. If not specified, the rule matches all load types. List loadType; ///A list of strings matched to the entire main document URL; limits the action to a specific list of URL patterns. ///Values must be lowercase ASCII, or punycode for non-ASCII. Can't be used with [ContentBlockerTrigger.unlessTopUrl]. List ifTopUrl; ///An array of strings matched to the entire main document URL; acts on any site except URL patterns in provided list. ///Values must be lowercase ASCII, or punycode for non-ASCII. Can't be used with [ContentBlockerTrigger.ifTopUrl]. List unlessTopUrl; ///An array of strings that specify loading contexts. /// ///*NOTE*: available only on iOS. List loadContext; ContentBlockerTrigger( {required this.urlFilter, this.ifFrameUrl = const [], this.urlFilterIsCaseSensitive = false, this.resourceType = const [], this.ifDomain = const [], this.unlessDomain = const [], this.loadType = const [], this.ifTopUrl = const [], this.unlessTopUrl = const [], this.loadContext = const []}) { assert(!(this.ifDomain.isEmpty || this.unlessDomain.isEmpty) == false); assert(this.loadType.length <= 2); assert(!(this.ifTopUrl.isEmpty || this.unlessTopUrl.isEmpty) == false); } Map toMap() { List resourceTypeStringList = []; resourceType.forEach((type) { resourceTypeStringList.add(type.toNativeValue()); }); List loadTypeStringList = []; loadType.forEach((type) { loadTypeStringList.add(type.toNativeValue()); }); List loadContextStringList = []; loadContext.forEach((type) { loadContextStringList.add(type.toNativeValue()); }); Map map = { "url-filter": urlFilter, "if-frame-url": ifFrameUrl, "url-filter-is-case-sensitive": urlFilterIsCaseSensitive, "if-domain": ifDomain, "unless-domain": unlessDomain, "resource-type": resourceTypeStringList, "load-type": loadTypeStringList, "if-top-url": ifTopUrl, "unless-top-url": unlessTopUrl, "load-context": loadContextStringList }; map.keys .where((key) => map[key] == null || (map[key] is List && (map[key] as List).length == 0)) // filter keys .toList() // create a copy to avoid concurrent modifications .forEach(map.remove); return map; } static ContentBlockerTrigger fromMap(Map map) { List resourceType = []; List loadType = []; List loadContext = []; List resourceTypeStringList = List.from(map["resource-type"] ?? []); resourceTypeStringList.forEach((typeValue) { var type = ContentBlockerTriggerResourceType.fromNativeValue(typeValue); if (type != null) { resourceType.add(type); } }); List loadTypeStringList = List.from(map["load-type"] ?? []); loadTypeStringList.forEach((typeValue) { var type = ContentBlockerTriggerLoadType.fromNativeValue(typeValue); if (type != null) { loadType.add(type); } }); List loadContextStringList = List.from(map["load-context"] ?? []); loadContextStringList.forEach((typeValue) { var context = ContentBlockerTriggerLoadContext.fromNativeValue(typeValue); if (context != null) { loadContext.add(context); } }); return ContentBlockerTrigger( urlFilter: map["url-filter"], ifFrameUrl: map["if-frame-url"], urlFilterIsCaseSensitive: map["url-filter-is-case-sensitive"], ifDomain: List.from(map["if-domain"] ?? []), unlessDomain: List.from(map["unless-domain"] ?? []), resourceType: resourceType, loadType: loadType, ifTopUrl: List.from(map["if-top-url"] ?? []), unlessTopUrl: List.from(map["unless-top-url"] ?? []), loadContext: loadContext); } } ///Action associated to the trigger. The action tells to the WebView what to do when the trigger is matched. ///When a trigger matches a resource, the browser queues the associated action for execution. ///The WebView evaluates all the triggers, it executes the actions in order. ///When a domain matches a trigger, all rules after the triggered rule that specify the same action are skipped. ///Group the rules with similar actions together to improve performance. class ContentBlockerAction { ///Type of the action. ContentBlockerActionType type; ///If the action type is [ContentBlockerActionType.CSS_DISPLAY_NONE], then also the [selector] property is required, otherwise it is ignored. ///It specify a string that defines a selector list. Use CSS identifiers as the individual selector values, separated by commas. String? selector; ContentBlockerAction({required this.type, this.selector}) { if (this.type == ContentBlockerActionType.CSS_DISPLAY_NONE) { assert(this.selector != null); } } Map toMap() { Map map = { "type": type.toNativeValue(), "selector": selector }; map.keys .where((key) => map[key] == null || (map[key] is List && (map[key] as List).length == 0)) // filter keys .toList() // create a copy to avoid concurrent modifications .forEach(map.remove); return map; } static ContentBlockerAction fromMap(Map map) { return ContentBlockerAction( type: ContentBlockerActionType.fromNativeValue(map["type"])!, selector: map["selector"]); } }