diff --git a/.all-contributorsrc b/.all-contributorsrc index 27fa03dad..33a5556f6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -766,6 +766,105 @@ "contributions": [ "code" ] + }, + { + "login": "nnnlog", + "name": "nlog (solrin)", + "avatar_url": "https://avatars.githubusercontent.com/u/20399222?v=4", + "profile": "https://nlog.dev", + "contributions": [ + "code" + ] + }, + { + "login": "Murmurl912", + "name": "Murmurl912", + "avatar_url": "https://avatars.githubusercontent.com/u/36264246?v=4", + "profile": "https://github.com/Murmurl912", + "contributions": [ + "code" + ] + }, + { + "login": "bschulz87", + "name": "Benjamin Schulz", + "avatar_url": "https://avatars.githubusercontent.com/u/30199362?v=4", + "profile": "https://github.com/bschulz87", + "contributions": [ + "ideas" + ] + }, + { + "login": "ShuheiSuzuki-07", + "name": "seal-app", + "avatar_url": "https://avatars.githubusercontent.com/u/118415919?v=4", + "profile": "https://github.com/ShuheiSuzuki-07", + "contributions": [ + "code" + ] + }, + { + "login": "takuyaaaaaaahaaaaaa", + "name": "Takuya Tominaga", + "avatar_url": "https://avatars.githubusercontent.com/u/31458194?v=4", + "profile": "https://github.com/takuyaaaaaaahaaaaaa", + "contributions": [ + "code" + ] + }, + { + "login": "yamaha252", + "name": "Sergey", + "avatar_url": "https://avatars.githubusercontent.com/u/4444068?v=4", + "profile": "https://github.com/yamaha252", + "contributions": [ + "code" + ] + }, + { + "login": "lyb5834", + "name": "yuanbo li", + "avatar_url": "https://avatars.githubusercontent.com/u/16265810?v=4", + "profile": "https://github.com/lyb5834", + "contributions": [ + "code" + ] + }, + { + "login": "Mecharyry", + "name": "Ryan Feline", + "avatar_url": "https://avatars.githubusercontent.com/u/3380092?v=4", + "profile": "https://github.com/Mecharyry", + "contributions": [ + "code" + ] + }, + { + "login": "fuzzybinary", + "name": "Jeff Ward", + "avatar_url": "https://avatars.githubusercontent.com/u/249982?v=4", + "profile": "https://fuzzybinary.com", + "contributions": [ + "test" + ] + }, + { + "login": "yerkejs", + "name": "Yelzhan Yerkebulan", + "avatar_url": "https://avatars.githubusercontent.com/u/33483071?v=4", + "profile": "https://hero.io", + "contributions": [ + "code" + ] + }, + { + "login": "GooRingX", + "name": "GooRingX", + "avatar_url": "https://avatars.githubusercontent.com/u/167741400?v=4", + "profile": "https://github.com/GooRingX", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 37a9593d5..13ffa0832 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -2,7 +2,7 @@ name: Close Stale Issues on: schedule: - - cron: '0 * * * *' # Run every hour + - cron: '0 0 * * *' # Run every day at midnight jobs: close-issues: diff --git a/README.md b/README.md index fe2d03493..6d63860ad 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![InAppWebView-logo](https://user-images.githubusercontent.com/5956938/195422744-bdcfed16-73f0-4bc9-94ab-ecf10771a1c4.png) -[![All Contributors](https://img.shields.io/badge/all_contributors-84-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-95-orange.svg?style=flat-square)](#contributors-) [![flutter_inappwebview version](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) @@ -191,6 +191,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Gray Mackall
Gray Mackall

💻 Pavel Mazhnik
Pavel Mazhnik

💻 + + nlog (solrin)
nlog (solrin)

💻 + Murmurl912
Murmurl912

💻 + Benjamin Schulz
Benjamin Schulz

🤔 + seal-app
seal-app

💻 + Takuya Tominaga
Takuya Tominaga

💻 + Sergey
Sergey

💻 + yuanbo li
yuanbo li

💻 + + + Ryan Feline
Ryan Feline

💻 + Jeff Ward
Jeff Ward

⚠️ + Yelzhan Yerkebulan
Yelzhan Yerkebulan

💻 + GooRingX
GooRingX

💻 + diff --git a/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md b/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md index b7586c319..81ce60287 100755 --- a/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md +++ b/dev_packages/flutter_inappwebview_internal_annotations/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 + +- Updated `ExchangeableEnum` and `ExchangeableObjectProperty`. + ## 1.1.1 - Added `ExchangeableObject.fromMapForceAllInline`. diff --git a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart index b69902497..ea66483b4 100644 --- a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart +++ b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_enum.dart @@ -4,6 +4,10 @@ class ExchangeableEnum { final bool fromValueMethod; final bool toNativeValueMethod; final bool fromNativeValueMethod; + final bool nameMethod; + final bool toNameMethod; + final bool byNameMethod; + final bool asNameMapMethod; final bool toStringMethod; final bool hashCodeMethod; final bool equalsOperator; @@ -15,6 +19,10 @@ class ExchangeableEnum { this.fromValueMethod = true, this.toNativeValueMethod = true, this.fromNativeValueMethod = true, + this.nameMethod = true, + this.toNameMethod = true, + this.byNameMethod = true, + this.asNameMapMethod = true, this.toStringMethod = true, this.hashCodeMethod = true, this.equalsOperator = true, diff --git a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart index 14fa2eae5..9ffd83955 100644 --- a/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart +++ b/dev_packages/flutter_inappwebview_internal_annotations/lib/src/exchangeable_object_property.dart @@ -1,9 +1,17 @@ class ExchangeableObjectProperty { final Function? serializer; final Function? deserializer; + final bool deprecatedUseNewFieldNameInConstructor; + final bool deprecatedUseNewFieldNameInFromMapMethod; + final bool leaveDeprecatedInFromMapMethod; + final bool leaveDeprecatedInToMapMethod; const ExchangeableObjectProperty({ this.serializer, - this.deserializer + this.deserializer, + this.deprecatedUseNewFieldNameInConstructor = true, + this.deprecatedUseNewFieldNameInFromMapMethod = true, + this.leaveDeprecatedInFromMapMethod = false, + this.leaveDeprecatedInToMapMethod = false, }); } diff --git a/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml b/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml index 215aca267..64e918ec7 100755 --- a/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml +++ b/dev_packages/flutter_inappwebview_internal_annotations/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_internal_annotations description: Internal annotations used by the generator of flutter_inappwebview plugin -version: 1.1.1 +version: 1.2.0 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: diff --git a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart index c7802cfae..2d602bba7 100644 --- a/dev_packages/generators/lib/src/exchangeable_enum_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_enum_generator.dart @@ -204,13 +204,13 @@ class ExchangeableEnumGenerator } if (annotation.read("fromValueMethod").boolValue && (!visitor.methods.containsKey("fromValue") || - Util.methodHasIgnore(visitor.methods['fromNativeValue']!))) { + Util.methodHasIgnore(visitor.methods['fromValue']!))) { final hasBitwiseOrOperator = annotation.read("bitwiseOrOperator").boolValue; classBuffer.writeln(""" ///Gets a possible [$extClassName] instance from [${enumValue.type}] value. static $extClassName? fromValue(${enumValue.type}${!Util.typeIsNullable(enumValue.type) ? '?' : ''} value) { - if (value != null) { + if (value != null) { try { return $extClassName.values .firstWhere((element) => element.toValue() == value); @@ -230,7 +230,7 @@ class ExchangeableEnumGenerator classBuffer.writeln(""" ///Gets a possible [$extClassName] instance from a native value. static $extClassName? fromNativeValue(${enumNativeValue.type}${!Util.typeIsNullable(enumNativeValue.type) ? '?' : ''} value) { - if (value != null) { + if (value != null) { try { return $extClassName.values .firstWhere((element) => element.toNativeValue() == value); @@ -243,6 +243,44 @@ class ExchangeableEnumGenerator """); } + if (annotation.read("nameMethod").boolValue && annotation.read("byNameMethod").boolValue && + (!visitor.methods.containsKey("byName") || Util.methodHasIgnore(visitor.methods['byName']!))) { + classBuffer.writeln(""" + /// Gets a possible [$extClassName] instance value with name [name]. + /// + /// Goes through [$extClassName.values] looking for a value with + /// name [name], as reported by [$extClassName.name]. + /// Returns the first value with the given name, otherwise `null`. + static $extClassName? byName(String? name) { + if (name != null) { + try { + return $extClassName.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + """); + } + + if (annotation.read("nameMethod").boolValue && annotation.read("asNameMapMethod").boolValue && + (!visitor.methods.containsKey("asNameMap") || Util.methodHasIgnore(visitor.methods['asNameMap']!))) { + classBuffer.writeln(""" + /// Creates a map from the names of [$extClassName] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + {for (final value in $extClassName.values) value.name(): value}; + """); + } + for (final entry in methodEntriesSorted) { final methodElement = entry.value; if (Util.methodHasIgnore(methodElement)) { @@ -282,6 +320,28 @@ class ExchangeableEnumGenerator """); } + if (annotation.read("nameMethod").boolValue && (!visitor.methods.containsKey("name") || + Util.methodHasIgnore(visitor.methods['name']!))) { + classBuffer.writeln('///Gets the name of the value.'); + classBuffer.writeln('String name() {'); + classBuffer.writeln('switch(_value) {'); + for (final entry in fieldEntriesSorted) { + final fieldName = entry.key; + final fieldElement = entry.value; + if (!fieldElement.isPrivate && fieldElement.isStatic) { + final fieldValue = fieldElement.computeConstantValue()?.getField("_value"); + dynamic constantValue = fieldValue?.toIntValue(); + if (enumValue.type.isDartCoreString) { + constantValue = "'${fieldValue?.toStringValue()}'"; + } + classBuffer.writeln("case $constantValue: return '$fieldName';"); + } + } + classBuffer.writeln('}'); + classBuffer.writeln('return _value.toString();'); + classBuffer.writeln('}'); + } + if (annotation.read("hashCodeMethod").boolValue && (!visitor.fields.containsKey("hashCode") || Util.methodHasIgnore(visitor.methods['hashCode']!))) { classBuffer.writeln(""" @@ -315,19 +375,23 @@ class ExchangeableEnumGenerator if (enumValue.type.isDartCoreString) { classBuffer.writeln('return _value;'); } else { - classBuffer.writeln('switch(_value) {'); - for (final entry in fieldEntriesSorted) { - final fieldName = entry.key; - final fieldElement = entry.value; - if (!fieldElement.isPrivate && fieldElement.isStatic) { - final fieldValue = - fieldElement.computeConstantValue()?.getField("_value"); - final constantValue = fieldValue?.toIntValue(); - classBuffer.writeln("case $constantValue: return '$fieldName';"); + if (annotation.read("nameMethod").boolValue) { + classBuffer.writeln('return name();'); + } else { + classBuffer.writeln('switch(_value) {'); + for (final entry in fieldEntriesSorted) { + final fieldName = entry.key; + final fieldElement = entry.value; + if (!fieldElement.isPrivate && fieldElement.isStatic) { + final fieldValue = + fieldElement.computeConstantValue()?.getField("_value"); + final constantValue = fieldValue?.toIntValue(); + classBuffer.writeln("case $constantValue: return '$fieldName';"); + } } + classBuffer.writeln('}'); + classBuffer.writeln('return _value.toString();'); } - classBuffer.writeln('}'); - classBuffer.writeln('return _value.toString();'); } classBuffer.writeln('}'); } diff --git a/dev_packages/generators/lib/src/exchangeable_object_generator.dart b/dev_packages/generators/lib/src/exchangeable_object_generator.dart index d399aaa12..498c95aa9 100644 --- a/dev_packages/generators/lib/src/exchangeable_object_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_object_generator.dart @@ -1,9 +1,10 @@ -import 'package:build/src/builder/build_step.dart'; -import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:source_gen/source_gen.dart'; +import 'package:build/src/builder/build_step.dart'; +import 'package:collection/collection.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'package:source_gen/source_gen.dart'; import 'model_visitor.dart'; import 'util.dart'; @@ -224,29 +225,43 @@ class ExchangeableObjectGenerator if (!hasCustomConstructor && deprecatedFields.length > 0) { classBuffer.writeln(' {'); for (final deprecatedField in deprecatedFields) { - final deprecatedFieldName = deprecatedField.name; + final deprecatedUseNewFieldNameInConstructor = _coreCheckerObjectProperty + .firstAnnotationOf(deprecatedField) + ?.getField("deprecatedUseNewFieldNameInConstructor") + ?.toBoolValue() ?? true; + if (!deprecatedUseNewFieldNameInConstructor) { + continue; + } + final message = _coreCheckerDeprecated .firstAnnotationOfExact(deprecatedField)! .getField("message")! - .toStringValue()!; - final fieldName = message + .toStringValue()!.trim(); + if (!message.startsWith("Use ") && !message.endsWith(" instead")) { + continue; + } + + final newFieldName = message .replaceFirst("Use ", "") .replaceFirst(" instead", "") .trim(); - final fieldElement = visitor.fields[fieldName]; - if (fieldElement != null) { - final fieldTypeElement = fieldElement.type.element; + + final newFieldElement = visitor.fields[newFieldName]; + final shouldUseNewFieldName = newFieldElement != null; + if (shouldUseNewFieldName) { + final deprecatedFieldName = deprecatedField.name; + final fieldTypeElement = newFieldElement.type.element; final deprecatedFieldTypeElement = deprecatedField.type.element; - final isNullable = Util.typeIsNullable(fieldElement.type); - var hasDefaultValue = (fieldElement is ParameterElement) - ? (fieldElement as ParameterElement).hasDefaultValue + final isNullable = Util.typeIsNullable(newFieldElement.type); + var hasDefaultValue = (newFieldElement is ParameterElement) + ? (newFieldElement as ParameterElement).hasDefaultValue : false; if (!isNullable && hasDefaultValue) { continue; } - classBuffer.write('$fieldName = $fieldName ?? '); + classBuffer.write('$newFieldName = $newFieldName ?? '); if (fieldTypeElement != null && deprecatedFieldTypeElement != null) { final deprecatedIsNullable = Util.typeIsNullable(deprecatedField.type); @@ -274,7 +289,7 @@ class ExchangeableObjectGenerator } else if (deprecatedField.type .getDisplayString(withNullability: false) == "Uri" && - fieldElement.type.getDisplayString(withNullability: false) == + newFieldElement.type.getDisplayString(withNullability: false) == "WebUri") { if (deprecatedIsNullable) { classBuffer.write( @@ -304,7 +319,8 @@ class ExchangeableObjectGenerator final nullable = annotation.read("nullableFromMapFactory").boolValue; classBuffer .writeln('static $extClassName${nullable ? '?' : ''} fromMap('); - classBuffer.writeln('Map${nullable ? '?' : ''} map'); + classBuffer.writeln( + 'Map${nullable ? '?' : ''} map, {EnumMethod? enumMethod}'); classBuffer.writeln(') {'); if (nullable) { classBuffer.writeln('if (map == null) { return null; }'); @@ -324,18 +340,59 @@ class ExchangeableObjectGenerator !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { var value = "map['$fieldName']"; - final deprecationMessage = _coreCheckerDeprecated - .firstAnnotationOfExact(fieldElement) - ?.getField("message") - ?.toStringValue(); - if (deprecationMessage != null) { - final newFieldName = deprecationMessage - .replaceFirst("Use ", "") - .replaceFirst(" instead", "") - .trim(); - value = "map['$newFieldName']"; + + if (fieldElement.hasDeprecated) { + final deprecatedUseNewFieldNameInFromMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("deprecatedUseNewFieldNameInFromMapMethod") + ?.toBoolValue() ?? true; + + final deprecationMessage = _coreCheckerDeprecated + .firstAnnotationOfExact(fieldElement) + ?.getField("message") + ?.toStringValue()?.trim(); + if (deprecationMessage != null && + deprecationMessage.startsWith("Use ") && + deprecationMessage.endsWith(" instead") && + deprecatedUseNewFieldNameInFromMapMethod) { + final newFieldName = deprecationMessage + .replaceFirst("Use ", "") + .replaceFirst(" instead", "") + .trim(); + final newFieldElement = fieldElements + .firstWhereOrNull((element) => element.name == newFieldName); + final shouldUseNewFieldName = newFieldElement != null && + (newFieldElement.type == fieldElement.type || + (fieldElement.name.startsWith(RegExp(r'android|ios')) && + fieldElement.name.toLowerCase().replaceFirst( + RegExp(r'android|ioswk|ios'), "") == + newFieldName.toLowerCase()) || + (newFieldElement.type.element != null && + fieldElement.type.element != null && + ((hasFromNativeValueMethod( + newFieldElement.type.element!) && + hasFromNativeValueMethod( + fieldElement.type.element!) || + (hasFromMapMethod( + newFieldElement.type.element!) && + hasFromMapMethod( + fieldElement.type.element!)))))); + if (shouldUseNewFieldName) { + value = "map['$newFieldName']"; + } + } else { + final leaveDeprecatedInFromMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("leaveDeprecatedInFromMapMethod") + ?.toBoolValue() ?? false; + if (!leaveDeprecatedInFromMapMethod) { + continue; + } + } } + final mapValue = value; + final customDeserializer = _coreCheckerObjectProperty .firstAnnotationOf(fieldElement) ?.getField("deserializer") @@ -345,9 +402,10 @@ class ExchangeableObjectGenerator customDeserializer.enclosingElement.name; if (deserializerClassName != null) { value = - "$deserializerClassName.${customDeserializer.name}($value)"; + "$deserializerClassName.${customDeserializer.name}($value, enumMethod: enumMethod)"; } else { - value = "${customDeserializer.name}($value)"; + value = + "${customDeserializer.name}($value, enumMethod: enumMethod)"; } } else { value = getFromMapValue(value, fieldElement.type); @@ -355,13 +413,23 @@ class ExchangeableObjectGenerator final constructorParameter = visitor.constructorParameters[fieldName]; final isRequiredParameter = constructorParameter != null && (constructorParameter.isRequiredNamed || - constructorParameter.isFinal || fieldElement.isFinal || + constructorParameter.isFinal || + fieldElement.isFinal || !Util.typeIsNullable(constructorParameter.type)) && !constructorParameter.hasDefaultValue; - if (isRequiredParameter || fieldElement.isFinal || annotation.read("fromMapForceAllInline").boolValue) { + if (isRequiredParameter || + fieldElement.isFinal || + annotation.read("fromMapForceAllInline").boolValue) { requiredFields.add('$fieldName: $value,'); } else { + final isFieldNullable = Util.typeIsNullable(fieldElement.type); + if (!isFieldNullable) { + nonRequiredFields.add("if ($mapValue != null) {"); + } nonRequiredFields.add("instance.$fieldName = $value;"); + if (!isFieldNullable) { + nonRequiredFields.add("}"); + } } } } @@ -401,13 +469,12 @@ class ExchangeableObjectGenerator (!visitor.methods.containsKey("toMap") || Util.methodHasIgnore(visitor.methods['toMap']!))) { classBuffer.writeln('///Converts instance to a map.'); - classBuffer.writeln('Map toMap() {'); + classBuffer.writeln('Map toMap({EnumMethod? enumMethod}) {'); classBuffer.writeln('return {'); final fieldElements = []; if (superClass != null) { for (final fieldElement in superClass.element.fields) { if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { @@ -418,7 +485,6 @@ class ExchangeableObjectGenerator for (final entry in fieldEntriesSorted) { final fieldElement = entry.value; if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { @@ -427,10 +493,19 @@ class ExchangeableObjectGenerator } for (final fieldElement in fieldElements) { if (!fieldElement.isPrivate && - !fieldElement.hasDeprecated && !fieldElement.isStatic && !(fieldElement.type.isDartCoreFunction || fieldElement.type is FunctionType)) { + if (fieldElement.hasDeprecated) { + final leaveDeprecatedInToMapMethod = _coreCheckerObjectProperty + .firstAnnotationOf(fieldElement) + ?.getField("leaveDeprecatedInToMapMethod") + ?.toBoolValue() ?? false; + if (!leaveDeprecatedInToMapMethod) { + continue; + } + } + final fieldName = fieldElement.name; var mapValue = fieldName; final customSerializer = _coreCheckerObjectProperty @@ -441,9 +516,9 @@ class ExchangeableObjectGenerator final serializerClassName = customSerializer.enclosingElement.name; if (serializerClassName != null) { mapValue = - "$serializerClassName.${customSerializer.name}($mapValue)"; + "$serializerClassName.${customSerializer.name}($mapValue, enumMethod: enumMethod)"; } else { - mapValue = "${customSerializer.name}($mapValue)"; + mapValue = "${customSerializer.name}($mapValue, enumMethod: enumMethod)"; } } else { mapValue = getToMapValue(fieldName, fieldElement.type); @@ -459,7 +534,7 @@ class ExchangeableObjectGenerator ?.getField("toMapMergeWith") ?.toBoolValue(); if (toMapMergeWith == true) { - classBuffer.writeln('...${methodElement.name}(),'); + classBuffer.writeln('...${methodElement.name}(enumMethod: enumMethod),'); } } classBuffer.writeln('};'); @@ -526,6 +601,8 @@ class ExchangeableObjectGenerator String getFromMapValue(String value, DartType elementType) { final fieldTypeElement = elementType.element; + // remove class reference terminating with "_" + final classNameReference = fieldTypeElement?.name?.replaceFirst("_", ""); final isNullable = Util.typeIsNullable(elementType); final displayString = elementType.getDisplayString(withNullability: false); if (displayString == "Uri") { @@ -592,23 +669,36 @@ class ExchangeableObjectGenerator return "$value${isNullable ? '?' : ''}.cast<${genericTypes.elementAt(0)}, ${genericTypes.elementAt(1)}>()"; } else if (fieldTypeElement != null && hasFromMapMethod(fieldTypeElement)) { final hasNullableFromMap = hasNullableFromMapFactory(fieldTypeElement); - // remove class reference terminating with "_" - return fieldTypeElement.name!.replaceFirst("_", "") + - ".fromMap($value?.cast())${!isNullable && hasNullableFromMap ? '!' : ''}"; + return classNameReference! + + ".fromMap($value?.cast(), enumMethod: enumMethod)${!isNullable && hasNullableFromMap ? '!' : ''}"; } else { - final hasFromValue = - fieldTypeElement != null && hasFromValueMethod(fieldTypeElement); - final hasFromNativeValue = fieldTypeElement != null && - hasFromNativeValueMethod(fieldTypeElement); - if (fieldTypeElement != null && (hasFromValue || hasFromNativeValue)) { - if (hasFromNativeValue) { - // remove class reference terminating with "_" - value = fieldTypeElement.name!.replaceFirst("_", "") + - '.fromNativeValue($value)'; + final hasFromValue = fieldTypeElement != null && hasFromValueMethod(fieldTypeElement); + final hasFromNativeValue = fieldTypeElement != null && hasFromNativeValueMethod(fieldTypeElement); + final hasByName = fieldTypeElement != null && hasByNameMethod(fieldTypeElement); + if (fieldTypeElement != null && (hasFromValue || hasFromNativeValue || hasByName)) { + if ([hasFromValue, hasFromNativeValue, hasByName].where((e) => e).length > 1) { + String? defaultEnumMethodValue = null; + if (hasFromNativeValue) { + defaultEnumMethodValue = "EnumMethod.nativeValue"; + } else if (hasFromValue) { + defaultEnumMethodValue = "EnumMethod.value"; + } else { + defaultEnumMethodValue = "EnumMethod.name"; + } + var wrapper = "switch (enumMethod ?? $defaultEnumMethodValue) {"; + wrapper += "EnumMethod.nativeValue => " + (hasFromNativeValue ? classNameReference! + '.fromNativeValue($value)' : "null") + ", "; + wrapper += "EnumMethod.value => " + (hasFromValue ? classNameReference! + '.fromValue($value)' : "null") + ", "; + wrapper += "EnumMethod.name => " + (hasByName ? classNameReference! + '.byName($value)' : "null"); + wrapper += "}"; + value = wrapper; } else { - // remove class reference terminating with "_" - value = fieldTypeElement.name!.replaceFirst("_", "") + - '.fromValue($value)'; + if (hasFromNativeValue) { + value = classNameReference! + '.fromNativeValue($value)'; + } else if (hasFromValue) { + value = classNameReference! + '.fromValue($value)'; + } else { + value = classNameReference! + '.byName($value)'; + } } if (!isNullable) { value += '!'; @@ -653,17 +743,35 @@ class ExchangeableObjectGenerator } else if (fieldTypeElement != null && hasToMapMethod(fieldTypeElement)) { return fieldName + (Util.typeIsNullable(elementType) ? '?' : '') + - '.toMap()'; + '.toMap(enumMethod: enumMethod)'; } else { - final hasToValue = - fieldTypeElement != null && hasToValueMethod(fieldTypeElement); - final hasToNativeValue = - fieldTypeElement != null && hasToNativeValueMethod(fieldTypeElement); - if (fieldTypeElement != null && (hasToValue || hasToNativeValue)) { - if (hasToNativeValue) { - return fieldName + (isNullable ? '?' : '') + '.toNativeValue()'; + final hasToValue = fieldTypeElement != null && hasToValueMethod(fieldTypeElement); + final hasToNativeValue = fieldTypeElement != null && hasToNativeValueMethod(fieldTypeElement); + final hasName = fieldTypeElement != null && hasNameMethod(fieldTypeElement); + if (fieldTypeElement != null && (hasToValue || hasToNativeValue || hasName)) { + if ([hasToValue, hasToNativeValue, hasName].where((e) => e).length > 1) { + String? defaultEnumMethodValue = null; + if (hasToNativeValue) { + defaultEnumMethodValue = "EnumMethod.nativeValue"; + } else if (hasToValue) { + defaultEnumMethodValue = "EnumMethod.value"; + } else { + defaultEnumMethodValue = "EnumMethod.name"; + } + var wrapper = "switch (enumMethod ?? $defaultEnumMethodValue) {"; + wrapper += "EnumMethod.nativeValue => " + (hasToNativeValue ? (fieldName + (isNullable ? '?' : '') + '.toNativeValue()') : "null") + ", "; + wrapper += "EnumMethod.value => " + (hasToValue ? (fieldName + (isNullable ? '?' : '') + '.toValue()') : "null") + ", "; + wrapper += "EnumMethod.name => " + (hasName ? (fieldName + (isNullable ? '?' : '') + '.name()') : "null"); + wrapper += "}"; + return wrapper; } else { - return fieldName + (isNullable ? '?' : '') + '.toValue()'; + if (hasToNativeValue) { + return fieldName + (isNullable ? '?' : '') + '.toNativeValue()'; + } else if (hasToValue) { + return fieldName + (isNullable ? '?' : '') + '.toValue()'; + } else { + return fieldName + (isNullable ? '?' : '') + '.name()'; + } } } } @@ -787,6 +895,28 @@ class ExchangeableObjectGenerator return false; } + bool hasByNameMethod(Element element) { + final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); + final byNameMethod = _coreCheckerEnum + .firstAnnotationOfExact(element) + ?.getField('byNameMethod') + ?.toBoolValue() ?? + false; + if (hasAnnotation && byNameMethod) { + return true; + } + + final fieldVisitor = ModelVisitor(); + element.visitChildren(fieldVisitor); + for (var entry in fieldVisitor.methods.entries) { + if (entry.key == "byName") { + return true; + } + } + + return false; + } + bool hasToValueMethod(Element element) { final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); final hasToValueMethod = _coreCheckerEnum @@ -830,4 +960,26 @@ class ExchangeableObjectGenerator return false; } + + bool hasNameMethod(Element element) { + final hasAnnotation = _coreCheckerEnum.hasAnnotationOf(element); + final hasNameMethod = _coreCheckerEnum + .firstAnnotationOfExact(element) + ?.getField('nameMethod') + ?.toBoolValue() ?? + false; + if (hasAnnotation && hasNameMethod) { + return true; + } + + final fieldVisitor = ModelVisitor(); + element.visitChildren(fieldVisitor); + for (var entry in fieldVisitor.methods.entries) { + if (entry.key == "name") { + return true; + } + } + + return false; + } } diff --git a/dev_packages/generators/pubspec.yaml b/dev_packages/generators/pubspec.yaml index ffa31b464..19bff4858 100755 --- a/dev_packages/generators/pubspec.yaml +++ b/dev_packages/generators/pubspec.yaml @@ -12,7 +12,9 @@ dependencies: sdk: flutter build: ^2.4.1 source_gen: ^1.5.0 - flutter_inappwebview_internal_annotations: ^1.1.1 + collection: any + flutter_inappwebview_internal_annotations: ^1.2.0 + # path: ../flutter_inappwebview_internal_annotations dev_dependencies: build_runner: ^2.4.12 diff --git a/flutter_inappwebview/CHANGELOG.md b/flutter_inappwebview/CHANGELOG.md index c74465f00..b45f23ceb 100755 --- a/flutter_inappwebview/CHANGELOG.md +++ b/flutter_inappwebview/CHANGELOG.md @@ -1,3 +1,165 @@ +## 6.2.0-beta.3 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.4.0-beta.2` -> `^1.4.0-beta.3` + - `flutter_inappwebview_android`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_ios`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_macos`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_web`: `^1.2.0-beta.2` -> `^1.2.0-beta.3` + - `flutter_inappwebview_windows`: `^0.7.0-beta.2` -> `^0.7.0-beta.3` +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +#### Platform Interface +- Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` +- Added `onShowFileChooser` WebView events + +#### Android Platform +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event +- Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) + +#### macOS and iOS Platforms +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Merged "Add proxy support for iOS" [#2362](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2362) (thanks to [yerkejs](https://github.com/yerkejs)) +- Fixed "[iOS] Webview opened with windowId does not receive javascript handler callback." [#2393](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2393) +- Fixed internal javascript callback handlers when the WebView has windowId not null +- macos: Fixed crash of unhandled `onPrintRequest` WebView event + +### Windows +- Merged "windows: fix WebViewEnvironment dispose crash" [#2433](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2433) (thanks to [GooRingX](https://github.com/GooRingX)) + +## 6.2.0-beta.2 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.4.0-beta.1` -> `^1.4.0-beta.2` + - `flutter_inappwebview_android`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_ios`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_macos`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_web`: `^1.2.0-beta.1` -> `^1.2.0-beta.2` + - `flutter_inappwebview_windows`: `^0.7.0-beta.1` -> `^0.7.0-beta.2` +- Fixed specific URLAuthenticationChallenge type for `onReceivedHttpAuthRequest`, `onReceivedServerTrustAuthRequest`, `onReceivedClientCertRequest` events of HeadlessInAppWebView +- Fixed missing return type for `InAppWebViewController.getJavaScriptBridgeName` static method + +#### Platform Interface +- Updated `flutter_inappwebview_internal_annotations` dependency from `^1.1.1` to `^1.2.0` +- Updated `fromMap` static method and `toMap` method implementations +- Updated all WebView events with return type `Future` to type `FutureOr` in order to not force the usage of `async` keyword +- Added `byName`, `name`, `asNameMap` custom enum classes methods +- Added `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled`, `alpha`, `isUserInteractionEnabled` properties to `InAppWebViewSettings` +- Added `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` methods to `PlatformWebViewEnvironment` class +- Added `isInterfaceSupported`, `setInputMethodEnabled`, `hideInputMethod`, `showInputMethod` methods to `PlatformInAppWebViewController` class +- Added `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties to `WebViewEnvironmentSettings` +- Added `onDownloadStarting` WebView event and deprecated `onDownloadStartRequest` event +- Added `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` events to `PlatformWebViewEnvironment` class +- Fixed missing PrintJobOrientation android values + +#### Android Platform +- Implemented `hideInputMethod`, `showInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "[Android] PrintJobOrientation _TypeError (type 'Null' is not a subtype of type 'int')" [#2413](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2413) +- Fixed "Accessibility Android" [#1694](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1694) +- Fixed "Automatic font scale according to accessibility option 'font size' of device does not work on Android" [#540](https://github.com/pichillilorenzo/flutter_inappwebview/issues/540) +- Fixed "callHandler method is not injected into InAppBrowser" [#1973](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1973) + +#### iOS Platform +- Implemented `setInputMethodEnabled`, `hideInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "In iOS version 17.2, when moving the input focus in a WebView, an unknown area appears at the top of the screen." [#1947](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947) + +#### macOS Platform +- Implemented `alpha` property of `InAppWebViewSettings` + +#### Windows Platform +- Updated Microsoft.Web.WebView2 SDK version from `1.0.2792.45` to `1.0.2849.39` +- Implemented `disableDefaultErrorPage`, `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `allowsBackForwardNavigationGestures`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled` properties of `InAppWebViewSettings` +- Implemented `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` WebViewEnvironment methods +- Implemented `isInterfaceSupported`, `getZoomScale` InAppWebViewController method +- Implemented `onDownloadStarting`, `onAcceleratorKeyPressed` WebView event +- Implemented `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties of `WebViewEnvironmentSettings` +- Implemented `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` WebViewEnvironment events +- Send mouse leave region event to native view +- Fixed wrong channel name when creating a `WebViewEnvironment` instance +- Fixed "[Windows] Has an overlay on the desktop when the application is minimized" [#2402](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2402) +- Fixed "[Windows] missing implementation of onPermissionRequest event will cause crash when requested by the webpage" [#2404](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2404) +- Fixed "Windows: getCookies return empty list" [#2314](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2314) + +## 6.2.0-beta.1 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_platform_interface`: `^1.3.0` -> `^1.4.0-beta.1` + - `flutter_inappwebview_android`: `^1.1.3` -> `^1.2.0-beta.1` + - `flutter_inappwebview_ios`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_macos`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_web`: `^1.1.2` -> `^1.2.0-beta.1` + - `flutter_inappwebview_windows`: `^0.6.0` -> `^0.7.0-beta.1` +- Fixed specific URLAuthenticationChallenge type for `onReceivedHttpAuthRequest`, `onReceivedServerTrustAuthRequest`, `onReceivedClientCertRequest` events + +Implemented security features to better manage access to the native javascript bridge. + +#### Platform Interface +- Updated static `fromMap` implementation for some classes +- Updated `kJavaScriptHandlerForbiddenNames` list +- Added `PlatformInAppLocalhostServer.onData` parameter to set a custom on data server callback +- Added `javaScriptBridgeEnabled`, `javaScriptBridgeOriginAllowList`, `javaScriptBridgeForMainFrameOnly`, `pluginScriptsOriginAllowList`, `pluginScriptsForMainFrameOnly`, `javaScriptHandlersOriginAllowList`, `javaScriptHandlersForMainFrameOnly`, `scrollMultiplier` InAppWebViewSettings parameters +- Added `setJavaScriptBridgeName`, `getJavaScriptBridgeName` static WebView controller methods +- Added `requestFocus` WebView method +- Added `onProcessFailed` WebView event +- Added `regexToAllowSyncUrlLoading` Android-specific property to `InAppWebViewSettings` +- Added `JavaScriptHandlerFunctionData` type +- Deprecated `JavaScriptHandlerCallback` type in favor of `JavaScriptHandlerFunction` type +- Deprecated `InAppWebViewSettings.forceDark` and `InAppWebViewSettings.forceDarkStrategy` Android-only properties in favor of `InAppWebViewSettings.algorithmicDarkeningAllowed` +- Fixed X509Certificate PEM base64 decoding + +#### Android Platform +- Added `InAppWebViewController.enableSlowWholeDocumentDraw` static method +- Added `CookieManager.flush` method +- Added support for `UserScript.forMainFrameOnly` parameter +- Implemented `requestFocus` WebView method +- Updated UserScript at document end implementation +- Updated `InAppWebViewController.takeScreenshot` implementation to support screenshot out of visible viewport when `InAppWebViewController.enableSlowWholeDocumentDraw` is called +- Fixed "After dispose a InAppWebViewKeepAlive using InAppWebViewController.disposeKeepAlive. NullPointerException is thrown when main activity enter destroyed state." [#2025](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2025) +- Fixed crash when trying to open InAppBrowser with R.menu.menu_main on release mode +- Fixed "android.webkit.WebSettingsWrapper cannot be cast to com.android.webview.chromium.ContentSettingsAdapter" [#2397](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2397) +- Merged "Prevent blank InAppBrowser Activity from being restored" [#1984](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1984) (thanks to [ShuheiSuzuki-07](https://github.com/ShuheiSuzuki-07)) +- Merged "Update Android Cookie Expiration date format to 24-hour format (HH)" [#2389](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2389) (thanks to [takuyaaaaaaahaaaaaa](https://github.com/takuyaaaaaaahaaaaaa)) +- Merged "[Android] allow sync navigation requests using a regular expression" [#2008](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2008) (thanks to [lyb5834](https://github.com/lyb5834)) + +#### macOS and iOS Platforms +- Implemented `requestFocus` WebView method +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Added support for `UserScript.allowedOriginRules` parameter +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- ios: Fixed `show`, `hide` methods and `hidden` setting for `InAppBrowser` +- macOS: Implemented also `clearFocus` WebView method +- macOS: Implemented workaround for "[macOS] Copy Shortcut does not work if TextField outside of WebView has focus" [#2380](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380) + +#### Windows Platform +- Updated `scrollMultiplier` default value from 6 to 1 +- Added support for `UserScript.allowedOriginRules` and `UserScript.forMainFrameOnly` parameters +- Implemented `onReceivedHttpAuthRequest`, `onReceivedClientCertRequest`, `onReceivedServerTrustAuthRequest`, `onRenderProcessGone`, `onRenderProcessUnresponsive`, `onWebContentProcessDidTerminate`, `onProcessFailed` WebView events +- Implemented `clearSslPreferences` WebView method +- Fixed `get_optional_fl_map_value` implementation in `utils/flutter.h` +- Fixed "Error in transparentBackground handling in Windows" [#2391](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2391) + +#### Web Platform +- Merged "[web] support iframe role and aria-hidden attributes" [2293](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2293) (thanks to [p-mazhnik](https://github.com/p-mazhnik)) +- Fixed 'Type 'int' is not a subtype of type 'JSValue' in type cast' when compiling/running using WASM + +## 6.1.5 + +- Updated dependencies to the latest versions for all platform implementations: + - `flutter_inappwebview_windows`: `^0.5.0` -> `^0.6.0` + +#### Windows Platform +- Updated code to support multiple flutter windows +- Fixed `InAppWebViewController.callAsyncJavaScript` not working with JSON objects +- Fixed `onLoadResourceWithCustomScheme` WebView event called every time + ## 6.1.4 - Updated dependencies to the latest versions for all platform implementations: diff --git a/flutter_inappwebview/README.md b/flutter_inappwebview/README.md index fe2d03493..6d63860ad 100755 --- a/flutter_inappwebview/README.md +++ b/flutter_inappwebview/README.md @@ -5,7 +5,7 @@ ![InAppWebView-logo](https://user-images.githubusercontent.com/5956938/195422744-bdcfed16-73f0-4bc9-94ab-ecf10771a1c4.png) -[![All Contributors](https://img.shields.io/badge/all_contributors-84-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-95-orange.svg?style=flat-square)](#contributors-) [![flutter_inappwebview version](https://img.shields.io/pub/v/flutter_inappwebview?include_prereleases)](https://pub.dartlang.org/packages/flutter_inappwebview) @@ -191,6 +191,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Gray Mackall
Gray Mackall

💻 Pavel Mazhnik
Pavel Mazhnik

💻 + + nlog (solrin)
nlog (solrin)

💻 + Murmurl912
Murmurl912

💻 + Benjamin Schulz
Benjamin Schulz

🤔 + seal-app
seal-app

💻 + Takuya Tominaga
Takuya Tominaga

💻 + Sergey
Sergey

💻 + yuanbo li
yuanbo li

💻 + + + Ryan Feline
Ryan Feline

💻 + Jeff Ward
Jeff Ward

⚠️ + Yelzhan Yerkebulan
Yelzhan Yerkebulan

💻 + GooRingX
GooRingX

💻 + diff --git a/flutter_inappwebview/example/android/app/build.gradle b/flutter_inappwebview/example/android/app/build.gradle index 50bdc4636..a50a94003 100755 --- a/flutter_inappwebview/example/android/app/build.gradle +++ b/flutter_inappwebview/example/android/app/build.gradle @@ -45,6 +45,10 @@ android { buildTypes { release { + // only for com.pichillilorenzo.flutter_inappwebview_android.R.menu.menu_main + minifyEnabled false + shrinkResources false + // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug diff --git a/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart b/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart index 4615f4573..78eb9c4dc 100644 --- a/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart +++ b/flutter_inappwebview/example/integration_test/chrome_safari_browser/open_and_close.dart @@ -40,7 +40,7 @@ void openAndClose() { activityButton: ActivityButton( templateImage: UIImage(systemName: "sun.max"), extensionIdentifier: - "com.pichillilorenzo.flutterinappwebview-ios-example.test"))); + "com.pichillilorenzo.flutterinappwebview-ios-example3.test"))); await chromeSafariBrowser.opened.future; expect(chromeSafariBrowser.isOpened(), true); expect(() async { diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/main.dart b/flutter_inappwebview/example/integration_test/in_app_webview/main.dart index 6f9239dbb..4f2b9a863 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/main.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/main.dart @@ -220,7 +220,7 @@ void main() { contentBlocker(); httpAuthCredentialDatabase(); onConsoleMessage(); - onDownloadStartRequest(); + onDownloadStarting(); javascriptDialogs(); onReceivedHttpError(); onLoadResourceWithCustomScheme(); diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart b/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart index ba735534d..952c0d0e7 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/on_download_start_request.dart @@ -1,6 +1,6 @@ part of 'main.dart'; -void onDownloadStartRequest() { +void onDownloadStarting() { final shouldSkip = kIsWeb ? true : ![ @@ -9,7 +9,7 @@ void onDownloadStartRequest() { TargetPlatform.macOS, ].contains(defaultTargetPlatform); - skippableTestWidgets('onDownloadStartRequest', (WidgetTester tester) async { + skippableTestWidgets('onDownloadStarting', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer onDownloadStartCompleter = Completer(); @@ -41,8 +41,9 @@ void onDownloadStartRequest() { onWebViewCreated: (controller) { controllerCompleter.complete(controller); }, - onDownloadStartRequest: (controller, request) { + onDownloadStarting: (controller, request) { onDownloadStartCompleter.complete(request.url.toString()); + return null; }, ), ), diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart b/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart index 078218caf..933bfc428 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/user_scripts.dart @@ -32,10 +32,14 @@ void userScripts() { UserScript( source: "var bar = 2;", injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, + forMainFrameOnly: + defaultTargetPlatform != TargetPlatform.android, contentWorld: ContentWorld.DEFAULT_CLIENT), UserScript( source: "var bar2 = 12;", injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, + forMainFrameOnly: + defaultTargetPlatform != TargetPlatform.android, contentWorld: ContentWorld.world(name: "test")), ]), onWebViewCreated: (controller) { diff --git a/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart b/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart index 914011ec4..36a25f410 100644 --- a/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart +++ b/flutter_inappwebview/example/integration_test/in_app_webview/web_archive.dart @@ -60,7 +60,9 @@ void webArchive() { controllerCompleter.complete(controller); }, onLoadStop: (controller, url) { - pageLoaded.complete(); + if (!pageLoaded.isCompleted) { + pageLoaded.complete(); + } }, ), ), diff --git a/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart b/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart index 349902e6f..8a8a4392c 100644 --- a/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart +++ b/flutter_inappwebview/example/integration_test/proxy_controller/clear_and_set_proxy_override.dart @@ -5,6 +5,8 @@ void clearAndSetProxyOverride() { ? true : ![ TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, ].contains(defaultTargetPlatform); skippableTestWidgets('clear and set proxy override', @@ -13,7 +15,7 @@ void clearAndSetProxyOverride() { Completer(); final Completer pageLoaded = Completer(); - var proxyAvailable = + var proxyAvailable = defaultTargetPlatform != TargetPlatform.android || await WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE); if (proxyAvailable) { diff --git a/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj b/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj index 6e356e166..2e0efe407 100644 --- a/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/flutter_inappwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -481,7 +481,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example2.test"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4.test"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -518,7 +518,7 @@ ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example2.test"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4.test"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -667,7 +667,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example2"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -702,7 +702,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example2"; + PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutterinappwebview-ios-example4"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart b/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart index 5bfa5db18..67a8f88c7 100755 --- a/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart +++ b/flutter_inappwebview/example/lib/headless_in_app_webview.screen.dart @@ -35,12 +35,12 @@ class _HeadlessInAppWebViewExampleScreenState onConsoleMessage: (controller, consoleMessage) { print("CONSOLE MESSAGE: " + consoleMessage.message); }, - onLoadStart: (controller, url) async { + onLoadStart: (controller, url) { setState(() { this.url = url.toString(); }); }, - onLoadStop: (controller, url) async { + onLoadStop: (controller, url) { setState(() { this.url = url.toString(); }); diff --git a/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart b/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart index f0870a903..73f893358 100755 --- a/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart +++ b/flutter_inappwebview/example/lib/in_app_browser_example.screen.dart @@ -22,20 +22,20 @@ class MyInAppBrowser extends InAppBrowser { ); @override - Future onBrowserCreated() async { + void onBrowserCreated() { print("\n\nBrowser Created!\n\n"); } @override - Future onLoadStart(url) async {} + void onLoadStart(url) {} @override - Future onLoadStop(url) async { + void onLoadStop(url) { pullToRefreshController?.endRefreshing(); } @override - Future onPermissionRequest(request) async { + FutureOr onPermissionRequest(request) { return PermissionResponse( resources: request.resources, action: PermissionResponseAction.GRANT); } @@ -58,8 +58,8 @@ class MyInAppBrowser extends InAppBrowser { } @override - Future shouldOverrideUrlLoading( - navigationAction) async { + FutureOr shouldOverrideUrlLoading( + navigationAction) { print("\n\nOverride ${navigationAction.request.url}\n\n"); return NavigationActionPolicy.ALLOW; } diff --git a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart index ce3dbcfd5..4acc6d966 100755 --- a/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart +++ b/flutter_inappwebview/example/lib/in_app_webiew_example.screen.dart @@ -1,4 +1,5 @@ import 'dart:collection'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -127,13 +128,13 @@ class _InAppWebViewExampleScreenState extends State { onWebViewCreated: (controller) async { webViewController = controller; }, - onLoadStart: (controller, url) async { + onLoadStart: (controller, url) { setState(() { this.url = url.toString(); urlController.text = this.url; }); }, - onPermissionRequest: (controller, request) async { + onPermissionRequest: (controller, request) { return PermissionResponse( resources: request.resources, action: PermissionResponseAction.GRANT); @@ -163,7 +164,7 @@ class _InAppWebViewExampleScreenState extends State { return NavigationActionPolicy.ALLOW; }, - onLoadStop: (controller, url) async { + onLoadStop: (controller, url) { pullToRefreshController?.endRefreshing(); setState(() { this.url = url.toString(); diff --git a/flutter_inappwebview/example/lib/main.dart b/flutter_inappwebview/example/lib/main.dart index ebb77618f..0a7802bd8 100755 --- a/flutter_inappwebview/example/lib/main.dart +++ b/flutter_inappwebview/example/lib/main.dart @@ -3,11 +3,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - import 'package:flutter_inappwebview_example/chrome_safari_browser_example.screen.dart'; import 'package:flutter_inappwebview_example/headless_in_app_webview.screen.dart'; -import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart'; import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart'; +import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart'; import 'package:flutter_inappwebview_example/web_authentication_session_example.screen.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -29,7 +28,18 @@ Future main() async { 'Failed to find an installed WebView2 runtime or non-stable Microsoft Edge installation.'); webViewEnvironment = await WebViewEnvironment.create( - settings: WebViewEnvironmentSettings(userDataFolder: 'custom_path')); + settings: WebViewEnvironmentSettings( + additionalBrowserArguments: kDebugMode + ? '--enable-features=msEdgeDevToolsWdpRemoteDebugging' + : null, + userDataFolder: 'custom_path', + )); + + webViewEnvironment?.onBrowserProcessExited = (detail) { + if (kDebugMode) { + print('Browser process exited with detail: $detail'); + } + }; } if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { diff --git a/flutter_inappwebview/example/macos/Runner/Info.plist b/flutter_inappwebview/example/macos/Runner/Info.plist index 4789daa6a..0c93eff43 100644 --- a/flutter_inappwebview/example/macos/Runner/Info.plist +++ b/flutter_inappwebview/example/macos/Runner/Info.plist @@ -22,6 +22,13 @@ $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile diff --git a/flutter_inappwebview/example/test/widget_test.dart b/flutter_inappwebview/example/test/widget_test.dart index 092d222f7..e69de29bb 100644 --- a/flutter_inappwebview/example/test/widget_test.dart +++ b/flutter_inappwebview/example/test/widget_test.dart @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/flutter_inappwebview/example/windows/runner/win32_window.cpp b/flutter_inappwebview/example/windows/runner/win32_window.cpp index 60608d0fe..786879fac 100644 --- a/flutter_inappwebview/example/windows/runner/win32_window.cpp +++ b/flutter_inappwebview/example/windows/runner/win32_window.cpp @@ -7,61 +7,64 @@ namespace { -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute + /// Window attribute that enables dark mode window decorations. + /// + /// Redefined in case the developer's machine has a Windows SDK older than + /// version 10.0.22000.0. + /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + /// Registry key for app theme preference. + /// + /// A value of 0 indicates apps should use dark mode. A non-zero or missing + /// value indicates apps should use light mode. + constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; + // The number of Win32Window objects that currently exist. + static int g_active_window_count = 0; -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; + // Scale helper to convert logical scaler values to physical using passed in + // scale factor + int Scale(int source, double scale_factor) + { + return static_cast(source * scale_factor); } - auto enable_non_client_dpi_scaling = + + // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. + // This API is only needed for PerMonitor V1 awareness mode. + void EnableFullDpiSupportIfAvailable(HWND hwnd) + { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); } - FreeLibrary(user32_module); -} } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { - public: +public: ~WindowClassRegistrar() = default; // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { + static WindowClassRegistrar* GetInstance() + { if (!instance_) { instance_ = new WindowClassRegistrar(); } @@ -76,7 +79,7 @@ class WindowClassRegistrar { // instances of the window. void UnregisterWindowClass(); - private: +private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; @@ -86,7 +89,8 @@ class WindowClassRegistrar { WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; -const wchar_t* WindowClassRegistrar::GetWindowClass() { +const wchar_t* WindowClassRegistrar::GetWindowClass() +{ if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); @@ -96,7 +100,7 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() { window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; @@ -106,39 +110,43 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() { return kWindowClassName; } -void WindowClassRegistrar::UnregisterWindowClass() { +void WindowClassRegistrar::UnregisterWindowClass() +{ UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } -Win32Window::Win32Window() { +Win32Window::Win32Window() +{ ++g_active_window_count; } -Win32Window::~Win32Window() { +Win32Window::~Win32Window() +{ --g_active_window_count; Destroy(); } bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { + const Point& origin, + const Size& size) +{ Destroy(); const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); + WindowClassRegistrar::GetInstance()->GetWindowClass(); - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; + const POINT target_point = { static_cast(origin.x), + static_cast(origin.y) }; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; @@ -149,24 +157,27 @@ bool Win32Window::Create(const std::wstring& title, return OnCreate(); } -bool Win32Window::Show() { +bool Win32Window::Show() +{ return ShowWindow(window_handle_, SW_SHOWNORMAL); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept +{ if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); + reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { + } + else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } @@ -175,53 +186,55 @@ LRESULT CALLBACK Win32Window::WndProc(HWND const window, LRESULT Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept +{ switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); } + return 0; + } - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } -void Win32Window::Destroy() { +void Win32Window::Destroy() +{ OnDestroy(); if (window_handle_) { @@ -233,56 +246,64 @@ void Win32Window::Destroy() { } } -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept +{ return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); + GetWindowLongPtr(window, GWLP_USERDATA)); } -void Win32Window::SetChildContent(HWND content) { +void Win32Window::SetChildContent(HWND content) +{ child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); + frame.bottom - frame.top, true); SetFocus(child_content_); } -RECT Win32Window::GetClientArea() { +RECT Win32Window::GetClientArea() +{ RECT frame; GetClientRect(window_handle_, &frame); return frame; } -HWND Win32Window::GetHandle() { +HWND Win32Window::GetHandle() +{ return window_handle_; } -void Win32Window::SetQuitOnClose(bool quit_on_close) { +void Win32Window::SetQuitOnClose(bool quit_on_close) +{ quit_on_close_ = quit_on_close; } -bool Win32Window::OnCreate() { +bool Win32Window::OnCreate() +{ // No-op; provided for subclasses. return true; } -void Win32Window::OnDestroy() { +void Win32Window::OnDestroy() +{ // No-op; provided for subclasses. } -void Win32Window::UpdateTheme(HWND const window) { +void Win32Window::UpdateTheme(HWND const window) +{ DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); if (result == ERROR_SUCCESS) { BOOL enable_dark_mode = light_mode == 0; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); + &enable_dark_mode, sizeof(enable_dark_mode)); } } diff --git a/flutter_inappwebview/lib/src/cookie_manager.dart b/flutter_inappwebview/lib/src/cookie_manager.dart index 3ab49df1e..bebf0f303 100755 --- a/flutter_inappwebview/lib/src/cookie_manager.dart +++ b/flutter_inappwebview/lib/src/cookie_manager.dart @@ -143,6 +143,9 @@ class CookieManager { ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.removeSessionCookies} Future removeSessionCookies() => platform.removeSessionCookies(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformCookieManager.flush} + Future flush() => platform.flush(); } ///Class that contains only iOS-specific methods of [CookieManager]. diff --git a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart index 3c0d8ca73..dbaeb6d63 100755 --- a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart +++ b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart @@ -199,96 +199,133 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @mustCallSuper void dispose() => platform.dispose(); + ///Use [onFormResubmission] instead. + @Deprecated('Use onFormResubmission instead') @override - Future? androidOnFormResubmission(Uri? url) { + FutureOr? androidOnFormResubmission(Uri? url) { return null; } + ///Use [onGeolocationPermissionsHidePrompt] instead. + @Deprecated("Use onGeolocationPermissionsHidePrompt instead") @override void androidOnGeolocationPermissionsHidePrompt() {} + ///Use [onGeolocationPermissionsShowPrompt] instead. + @Deprecated("Use onGeolocationPermissionsShowPrompt instead") @override - Future? + FutureOr? androidOnGeolocationPermissionsShowPrompt(String origin) { return null; } + ///Use [onJsBeforeUnload] instead. + @Deprecated('Use onJsBeforeUnload instead') @override - Future? androidOnJsBeforeUnload( + FutureOr? androidOnJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } + ///Use [onPermissionRequest] instead. + @Deprecated("Use onPermissionRequest instead") @override - Future? androidOnPermissionRequest( + FutureOr? androidOnPermissionRequest( String origin, List resources) { return null; } + ///Use [onReceivedIcon] instead. + @Deprecated('Use onReceivedIcon instead') @override void androidOnReceivedIcon(Uint8List icon) {} + ///Use [onReceivedLoginRequest] instead. + @Deprecated('Use onReceivedLoginRequest instead') @override void androidOnReceivedLoginRequest(LoginRequest loginRequest) {} + ///Use [onReceivedTouchIconUrl] instead. + @Deprecated('Use onReceivedTouchIconUrl instead') @override void androidOnReceivedTouchIconUrl(Uri url, bool precomposed) {} + ///Use [onRenderProcessGone] instead. + @Deprecated("Use onRenderProcessGone instead") @override void androidOnRenderProcessGone(RenderProcessGoneDetail detail) {} + ///Use [onRenderProcessResponsive] instead. + @Deprecated("Use onRenderProcessResponsive instead") @override - Future? androidOnRenderProcessResponsive( + FutureOr? androidOnRenderProcessResponsive( Uri? url) { return null; } + ///Use [onRenderProcessUnresponsive] instead. + @Deprecated("Use onRenderProcessUnresponsive instead") @override - Future? androidOnRenderProcessUnresponsive( + FutureOr? androidOnRenderProcessUnresponsive( Uri? url) { return null; } + ///Use [onSafeBrowsingHit] instead. + @Deprecated("Use onSafeBrowsingHit instead") @override - Future? androidOnSafeBrowsingHit( + FutureOr? androidOnSafeBrowsingHit( Uri url, SafeBrowsingThreat? threatType) { return null; } + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use onZoomScaleChanged instead') @override void androidOnScaleChanged(double oldScale, double newScale) {} + ///Use [shouldInterceptRequest] instead. + @Deprecated("Use shouldInterceptRequest instead") @override - Future? androidShouldInterceptRequest( + FutureOr? androidShouldInterceptRequest( WebResourceRequest request) { return null; } + ///Use [onDidReceiveServerRedirectForProvisionalNavigation] instead. + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') @override void iosOnDidReceiveServerRedirectForProvisionalNavigation() {} + ///Use [onNavigationResponse] instead. + @Deprecated('Use onNavigationResponse instead') @override - Future? iosOnNavigationResponse( + FutureOr? iosOnNavigationResponse( IOSWKNavigationResponse navigationResponse) { return null; } + ///Use [onWebContentProcessDidTerminate] instead. + @Deprecated('Use onWebContentProcessDidTerminate instead') @override void iosOnWebContentProcessDidTerminate() {} + ///Use [shouldAllowDeprecatedTLS] instead. + @Deprecated('Use shouldAllowDeprecatedTLS instead') @override - Future? iosShouldAllowDeprecatedTLS( + FutureOr? iosShouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @override - Future? onAjaxProgress(AjaxRequest ajaxRequest) { + FutureOr? onAjaxProgress(AjaxRequest ajaxRequest) { return null; } @override - Future? onAjaxReadyStateChange(AjaxRequest ajaxRequest) { + FutureOr? onAjaxReadyStateChange( + AjaxRequest ajaxRequest) { return null; } @@ -309,19 +346,29 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} @override - Future? onCreateWindow(CreateWindowAction createWindowAction) { + FutureOr? onCreateWindow(CreateWindowAction createWindowAction) { return null; } @override void onDidReceiveServerRedirectForProvisionalNavigation() {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') @override void onDownloadStart(Uri url) {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') @override void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + @override + FutureOr? onDownloadStarting( + DownloadStartRequest downloadStartRequest) { + return null; + } + @override void onEnterFullscreen() {} @@ -331,12 +378,14 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @override void onExitFullscreen() {} + ///Use [FindInteractionController.onFindResultReceived] instead. + @Deprecated('Use FindInteractionController.onFindResultReceived instead') @override void onFindResultReceived( int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting) {} @override - Future? onFormResubmission(WebUri? url) { + FutureOr? onFormResubmission(WebUri? url) { return null; } @@ -344,48 +393,54 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onGeolocationPermissionsHidePrompt() {} @override - Future? + FutureOr? onGeolocationPermissionsShowPrompt(String origin) { return null; } @override - Future? onJsAlert(JsAlertRequest jsAlertRequest) { + FutureOr? onJsAlert(JsAlertRequest jsAlertRequest) { return null; } @override - Future? onJsBeforeUnload( + FutureOr? onJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @override - Future? onJsConfirm(JsConfirmRequest jsConfirmRequest) { + FutureOr? onJsConfirm(JsConfirmRequest jsConfirmRequest) { return null; } @override - Future? onJsPrompt(JsPromptRequest jsPromptRequest) { + FutureOr? onJsPrompt(JsPromptRequest jsPromptRequest) { return null; } + ///Use [onReceivedError] instead. + @Deprecated("Use onReceivedError instead") @override void onLoadError(Uri? url, int code, String message) {} + ///Use [onReceivedHttpError] instead. + @Deprecated("Use onReceivedHttpError instead") @override void onLoadHttpError(Uri? url, int statusCode, String description) {} @override void onLoadResource(LoadedResource resource) {} + ///Use [onLoadResourceWithCustomScheme] instead. + @Deprecated('Use onLoadResourceWithCustomScheme instead') @override - Future? onLoadResourceCustomScheme(Uri url) { + FutureOr? onLoadResourceCustomScheme(Uri url) { return null; } @override - Future? onLoadResourceWithCustomScheme( + FutureOr? onLoadResourceWithCustomScheme( WebResourceRequest request) { return null; } @@ -404,7 +459,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { MediaCaptureState? oldState, MediaCaptureState? newState) {} @override - Future? onNavigationResponse( + FutureOr? onNavigationResponse( NavigationResponse navigationResponse) { return null; } @@ -416,7 +471,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onPageCommitVisible(WebUri? url) {} @override - Future? onPermissionRequest( + FutureOr? onPermissionRequest( PermissionRequest permissionRequest) { return null; } @@ -424,11 +479,13 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @override void onPermissionRequestCanceled(PermissionRequest permissionRequest) {} + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") @override void onPrint(Uri? url) {} @override - Future? onPrintRequest( + FutureOr? onPrintRequest( WebUri? url, PlatformPrintJobController? printJobController) { return null; } @@ -437,8 +494,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onProgressChanged(int progress) {} @override - Future? onReceivedClientCertRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedClientCertRequest( + ClientCertChallenge challenge) { return null; } @@ -446,8 +503,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onReceivedError(WebResourceRequest request, WebResourceError error) {} @override - Future? onReceivedHttpAuthRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedHttpAuthRequest( + HttpAuthenticationChallenge challenge) { return null; } @@ -462,8 +519,8 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onReceivedLoginRequest(LoginRequest loginRequest) {} @override - Future? onReceivedServerTrustAuthRequest( - URLAuthenticationChallenge challenge) { + FutureOr? onReceivedServerTrustAuthRequest( + ServerTrustChallenge challenge) { return null; } @@ -474,12 +531,13 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onRenderProcessGone(RenderProcessGoneDetail detail) {} @override - Future? onRenderProcessResponsive(WebUri? url) { + FutureOr? onRenderProcessResponsive( + WebUri? url) { return null; } @override - Future? onRenderProcessUnresponsive( + FutureOr? onRenderProcessUnresponsive( WebUri? url) { return null; } @@ -488,7 +546,7 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onRequestFocus() {} @override - Future? onSafeBrowsingHit( + FutureOr? onSafeBrowsingHit( WebUri url, SafeBrowsingThreat? threatType) { return null; } @@ -515,34 +573,45 @@ class InAppBrowser implements PlatformInAppBrowserEvents { void onZoomScaleChanged(double oldScale, double newScale) {} @override - Future? shouldAllowDeprecatedTLS( + FutureOr? shouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @override - Future? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { + FutureOr? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { return null; } @override - Future? shouldInterceptFetchRequest( + FutureOr? shouldInterceptFetchRequest( FetchRequest fetchRequest) { return null; } @override - Future? shouldInterceptRequest( + FutureOr? shouldInterceptRequest( WebResourceRequest request) { return null; } @override - Future? shouldOverrideUrlLoading( + FutureOr? shouldOverrideUrlLoading( NavigationAction navigationAction) { return null; } @override void onMainWindowWillClose() {} + + @override + void onProcessFailed(ProcessFailedDetail detail) {} + + @override + void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + @override + FutureOr onShowFileChooser(ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview/lib/src/in_app_localhost_server.dart b/flutter_inappwebview/lib/src/in_app_localhost_server.dart index daae11b7b..841f0cc29 100755 --- a/flutter_inappwebview/lib/src/in_app_localhost_server.dart +++ b/flutter_inappwebview/lib/src/in_app_localhost_server.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer} @@ -9,12 +10,14 @@ class InAppLocalhostServer { String directoryIndex = 'index.html', String documentRoot = './', bool shared = false, + Future Function(HttpRequest request)? onData, }) : this.fromPlatformCreationParams( PlatformInAppLocalhostServerCreationParams( port: port, directoryIndex: directoryIndex, documentRoot: documentRoot, - shared: shared), + shared: shared, + onData: onData), ); /// Constructs a [InAppLocalhostServer] from creation params for a specific @@ -42,6 +45,9 @@ class InAppLocalhostServer { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.shared} bool get shared => platform.shared; + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.onData} + Future Function(HttpRequest request)? get onData => platform.onData; + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppLocalhostServer.start} Future start() => platform.start(); diff --git a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart index f7c216624..252f3e5f8 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,13 +1,13 @@ +import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + import '../find_interaction/find_interaction_controller.dart'; +import '../pull_to_refresh/pull_to_refresh_controller.dart'; import '../webview_environment/webview_environment.dart'; import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/pull_to_refresh_controller.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} class HeadlessInAppWebView { @@ -37,264 +37,139 @@ class HeadlessInAppWebView { platform: webViewControllerPlatform); } - HeadlessInAppWebView({ - Size initialSize = const Size(-1, -1), - int? windowId, - HeadlessInAppWebView? headlessWebView, - InAppWebViewKeepAlive? keepAlive, - bool? preventGestureDelay, - WebViewEnvironment? webViewEnvironment, - @Deprecated('Use onGeolocationPermissionsHidePrompt instead') - void Function(InAppWebViewController controller)? - androidOnGeolocationPermissionsHidePrompt, - @Deprecated('Use onGeolocationPermissionsShowPrompt instead') - Future Function( - InAppWebViewController controller, String origin)? - androidOnGeolocationPermissionsShowPrompt, - @Deprecated('Use onPermissionRequest instead') - Future Function( - InAppWebViewController controller, - String origin, - List resources)? - androidOnPermissionRequest, - @Deprecated('Use onSafeBrowsingHit instead') - Future Function(InAppWebViewController controller, - Uri url, SafeBrowsingThreat? threatType)? - androidOnSafeBrowsingHit, - InAppWebViewInitialData? initialData, - String? initialFile, - @Deprecated('Use initialSettings instead') - InAppWebViewGroupOptions? initialOptions, - InAppWebViewSettings? initialSettings, - URLRequest? initialUrlRequest, - UnmodifiableListView? initialUserScripts, - PullToRefreshController? pullToRefreshController, - FindInteractionController? findInteractionController, - ContextMenu? contextMenu, - void Function(InAppWebViewController controller, WebUri? url)? - onPageCommitVisible, - void Function(InAppWebViewController controller, String? title)? - onTitleChanged, - @Deprecated( - 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') - void Function(InAppWebViewController controller)? - iosOnDidReceiveServerRedirectForProvisionalNavigation, - @Deprecated('Use onWebContentProcessDidTerminate instead') - void Function(InAppWebViewController controller)? - iosOnWebContentProcessDidTerminate, - @Deprecated('Use onNavigationResponse instead') - Future Function( - InAppWebViewController controller, - IOSWKNavigationResponse navigationResponse)? - iosOnNavigationResponse, - @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - iosShouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxProgress, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxReadyStateChange, - void Function( - InAppWebViewController controller, ConsoleMessage consoleMessage)? - onConsoleMessage, - Future Function(InAppWebViewController controller, - CreateWindowAction createWindowAction)? - onCreateWindow, - void Function(InAppWebViewController controller)? onCloseWindow, - void Function(InAppWebViewController controller)? onWindowFocus, - void Function(InAppWebViewController controller)? onWindowBlur, - @Deprecated('Use onReceivedIcon instead') - void Function(InAppWebViewController controller, Uint8List icon)? - androidOnReceivedIcon, - @Deprecated('Use onReceivedTouchIconUrl instead') - void Function(InAppWebViewController controller, Uri url, bool precomposed)? - androidOnReceivedTouchIconUrl, - @Deprecated('Use onDownloadStartRequest instead') - void Function(InAppWebViewController controller, Uri url)? onDownloadStart, - void Function(InAppWebViewController controller, - DownloadStartRequest downloadStartRequest)? - onDownloadStartRequest, - @Deprecated('Use FindInteractionController.onFindResultReceived instead') - void Function(InAppWebViewController controller, int activeMatchOrdinal, - int numberOfMatches, bool isDoneCounting)? - onFindResultReceived, - Future Function( - InAppWebViewController controller, JsAlertRequest jsAlertRequest)? - onJsAlert, - Future Function(InAppWebViewController controller, - JsConfirmRequest jsConfirmRequest)? - onJsConfirm, - Future Function( - InAppWebViewController controller, JsPromptRequest jsPromptRequest)? - onJsPrompt, - @Deprecated("Use onReceivedError instead") - void Function(InAppWebViewController controller, Uri? url, int code, - String message)? - onLoadError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceError error)? - onReceivedError, - @Deprecated("Use onReceivedHttpError instead") - void Function(InAppWebViewController controller, Uri? url, int statusCode, - String description)? - onLoadHttpError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceResponse errorResponse)? - onReceivedHttpError, - void Function(InAppWebViewController controller, LoadedResource resource)? - onLoadResource, - @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future Function( - InAppWebViewController controller, Uri url)? - onLoadResourceCustomScheme, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - onLoadResourceWithCustomScheme, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, - void Function(InAppWebViewController controller, - InAppWebViewHitTestResult hitTestResult)? - onLongPressHitTestResult, - @Deprecated("Use onPrintRequest instead") - void Function(InAppWebViewController controller, Uri? url)? onPrint, - Future Function(InAppWebViewController controller, WebUri? url, - PlatformPrintJobController? printJobController)? - onPrintRequest, - void Function(InAppWebViewController controller, int progress)? - onProgressChanged, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedClientCertRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedHttpAuthRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedServerTrustAuthRequest, - void Function(InAppWebViewController controller, int x, int y)? - onScrollChanged, - void Function( - InAppWebViewController controller, WebUri? url, bool? isReload)? - onUpdateVisitedHistory, - void Function(InAppWebViewController controller)? onWebViewCreated, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - shouldInterceptAjaxRequest, - Future Function( - InAppWebViewController controller, FetchRequest fetchRequest)? - shouldInterceptFetchRequest, - Future Function(InAppWebViewController controller, - NavigationAction navigationAction)? - shouldOverrideUrlLoading, - void Function(InAppWebViewController controller)? onEnterFullscreen, - void Function(InAppWebViewController controller)? onExitFullscreen, - void Function(InAppWebViewController controller, int x, int y, - bool clampedX, bool clampedY)? - onOverScrolled, - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - onZoomScaleChanged, - @Deprecated('Use shouldInterceptRequest instead') - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - androidShouldInterceptRequest, - @Deprecated('Use onRenderProcessUnresponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessUnresponsive, - @Deprecated('Use onRenderProcessResponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessResponsive, - @Deprecated('Use onRenderProcessGone instead') - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - androidOnRenderProcessGone, - @Deprecated('Use onFormResubmission instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnFormResubmission, - @Deprecated('Use onZoomScaleChanged instead') - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - androidOnScaleChanged, - @Deprecated('Use onJsBeforeUnload instead') - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - androidOnJsBeforeUnload, - @Deprecated('Use onReceivedLoginRequest instead') - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - androidOnReceivedLoginRequest, - void Function(InAppWebViewController controller)? - onDidReceiveServerRedirectForProvisionalNavigation, - Future Function( - InAppWebViewController controller, WebUri? url)? - onFormResubmission, - void Function(InAppWebViewController controller)? - onGeolocationPermissionsHidePrompt, - Future Function( - InAppWebViewController controller, String origin)? - onGeolocationPermissionsShowPrompt, - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - onJsBeforeUnload, - Future Function( - InAppWebViewController controller, - NavigationResponse navigationResponse)? - onNavigationResponse, - Future Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequest, - void Function(InAppWebViewController controller, Uint8List icon)? - onReceivedIcon, - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - onReceivedLoginRequest, - void Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequestCanceled, - void Function(InAppWebViewController controller)? onRequestFocus, - void Function( - InAppWebViewController controller, WebUri url, bool precomposed)? - onReceivedTouchIconUrl, - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - onRenderProcessGone, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessResponsive, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessUnresponsive, - Future Function(InAppWebViewController controller, - WebUri url, SafeBrowsingThreat? threatType)? - onSafeBrowsingHit, - void Function(InAppWebViewController controller)? - onWebContentProcessDidTerminate, - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - shouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - shouldInterceptRequest, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onCameraCaptureStateChanged, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onMicrophoneCaptureStateChanged, - void Function(InAppWebViewController controller, Size oldContentSize, - Size newContentSize)? - onContentSizeChanged, - }) : this.fromPlatformCreationParams( + HeadlessInAppWebView( + {Size initialSize = const Size(-1, -1), + int? windowId, + HeadlessInAppWebView? headlessWebView, + InAppWebViewKeepAlive? keepAlive, + bool? preventGestureDelay, + WebViewEnvironment? webViewEnvironment, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + void Function(InAppWebViewController controller)? + androidOnGeolocationPermissionsHidePrompt, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + FutureOr Function( + InAppWebViewController controller, String origin)? + androidOnGeolocationPermissionsShowPrompt, + @Deprecated('Use onPermissionRequest instead') + FutureOr Function( + InAppWebViewController controller, + String origin, + List resources)? + androidOnPermissionRequest, + @Deprecated('Use onSafeBrowsingHit instead') + FutureOr Function( + InAppWebViewController controller, + Uri url, + SafeBrowsingThreat? threatType)? + androidOnSafeBrowsingHit, + InAppWebViewInitialData? initialData, + String? initialFile, + @Deprecated('Use initialSettings instead') + InAppWebViewGroupOptions? initialOptions, + InAppWebViewSettings? initialSettings, + URLRequest? initialUrlRequest, + UnmodifiableListView? initialUserScripts, + PullToRefreshController? pullToRefreshController, + FindInteractionController? findInteractionController, + ContextMenu? contextMenu, + void Function(InAppWebViewController controller, WebUri? url)? + onPageCommitVisible, + void Function(InAppWebViewController controller, String? title)? + onTitleChanged, + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') + void Function(InAppWebViewController controller)? + iosOnDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onWebContentProcessDidTerminate instead') + void Function(InAppWebViewController controller)? + iosOnWebContentProcessDidTerminate, + @Deprecated('Use onNavigationResponse instead') + FutureOr Function(InAppWebViewController controller, IOSWKNavigationResponse navigationResponse)? iosOnNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? iosShouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxProgress, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxReadyStateChange, + void Function(InAppWebViewController controller, ConsoleMessage consoleMessage)? onConsoleMessage, + FutureOr Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow, + void Function(InAppWebViewController controller)? onCloseWindow, + void Function(InAppWebViewController controller)? onWindowFocus, + void Function(InAppWebViewController controller)? onWindowBlur, + @Deprecated('Use onReceivedIcon instead') void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') void Function(InAppWebViewController controller, Uri url, bool precomposed)? androidOnReceivedTouchIconUrl, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, Uri url)? onDownloadStart, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStartRequest, + FutureOr Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStarting, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') void Function(InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting)? onFindResultReceived, + FutureOr Function(InAppWebViewController controller, JsAlertRequest jsAlertRequest)? onJsAlert, + FutureOr Function(InAppWebViewController controller, JsConfirmRequest jsConfirmRequest)? onJsConfirm, + FutureOr Function(InAppWebViewController controller, JsPromptRequest jsPromptRequest)? onJsPrompt, + @Deprecated("Use onReceivedError instead") void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceError error)? onReceivedError, + @Deprecated("Use onReceivedHttpError instead") void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError, + void Function(InAppWebViewController controller, LoadedResource resource)? onLoadResource, + @Deprecated('Use onLoadResourceWithCustomScheme instead') FutureOr Function(InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, + void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult, + @Deprecated("Use onPrintRequest instead") void Function(InAppWebViewController controller, Uri? url)? onPrint, + FutureOr Function(InAppWebViewController controller, WebUri? url, PlatformPrintJobController? printJobController)? onPrintRequest, + void Function(InAppWebViewController controller, int progress)? onProgressChanged, + FutureOr Function(InAppWebViewController controller, ClientCertChallenge challenge)? onReceivedClientCertRequest, + FutureOr Function(InAppWebViewController controller, HttpAuthenticationChallenge challenge)? onReceivedHttpAuthRequest, + FutureOr Function(InAppWebViewController controller, ServerTrustChallenge challenge)? onReceivedServerTrustAuthRequest, + void Function(InAppWebViewController controller, int x, int y)? onScrollChanged, + void Function(InAppWebViewController controller, WebUri? url, bool? isReload)? onUpdateVisitedHistory, + void Function(InAppWebViewController controller)? onWebViewCreated, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? shouldInterceptAjaxRequest, + FutureOr Function(InAppWebViewController controller, FetchRequest fetchRequest)? shouldInterceptFetchRequest, + FutureOr Function(InAppWebViewController controller, NavigationAction navigationAction)? shouldOverrideUrlLoading, + void Function(InAppWebViewController controller)? onEnterFullscreen, + void Function(InAppWebViewController controller)? onExitFullscreen, + void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled, + void Function(InAppWebViewController controller, double oldScale, double newScale)? onZoomScaleChanged, + @Deprecated('Use shouldInterceptRequest instead') FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? androidShouldInterceptRequest, + @Deprecated('Use onRenderProcessUnresponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessUnresponsive, + @Deprecated('Use onRenderProcessResponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessResponsive, + @Deprecated('Use onRenderProcessGone instead') void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? androidOnRenderProcessGone, + @Deprecated('Use onFormResubmission instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') void Function(InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged, + @Deprecated('Use onJsBeforeUnload instead') FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? androidOnJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') void Function(InAppWebViewController controller, LoginRequest loginRequest)? androidOnReceivedLoginRequest, + void Function(InAppWebViewController controller)? onDidReceiveServerRedirectForProvisionalNavigation, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onFormResubmission, + void Function(InAppWebViewController controller)? onGeolocationPermissionsHidePrompt, + FutureOr Function(InAppWebViewController controller, String origin)? onGeolocationPermissionsShowPrompt, + FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? onJsBeforeUnload, + FutureOr Function(InAppWebViewController controller, NavigationResponse navigationResponse)? onNavigationResponse, + FutureOr Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest, + void Function(InAppWebViewController controller, Uint8List icon)? onReceivedIcon, + void Function(InAppWebViewController controller, LoginRequest loginRequest)? onReceivedLoginRequest, + void Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequestCanceled, + void Function(InAppWebViewController controller)? onRequestFocus, + void Function(InAppWebViewController controller, WebUri url, bool precomposed)? onReceivedTouchIconUrl, + void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? onRenderProcessGone, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessResponsive, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessUnresponsive, + FutureOr Function(InAppWebViewController controller, WebUri url, SafeBrowsingThreat? threatType)? onSafeBrowsingHit, + void Function(InAppWebViewController controller)? onWebContentProcessDidTerminate, + FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? shouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged, + void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, + void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) + : this.fromPlatformCreationParams( params: PlatformHeadlessInAppWebViewCreationParams( controllerFromPlatform: (PlatformInAppWebViewController controller) => InAppWebViewController.fromPlatform(platform: controller), @@ -361,6 +236,10 @@ class HeadlessInAppWebView { ? (controller, downloadStartRequest) => onDownloadStartRequest.call(controller, downloadStartRequest) : null, + onDownloadStarting: onDownloadStarting != null + ? (controller, downloadStartRequest) => + onDownloadStarting.call(controller, downloadStartRequest) + : null, onLoadResourceCustomScheme: onLoadResourceCustomScheme != null ? (controller, url) => onLoadResourceCustomScheme.call(controller, url) @@ -636,6 +515,17 @@ class HeadlessInAppWebView { onContentSizeChanged.call( controller, oldContentSize, newContentSize) : null, + onProcessFailed: onProcessFailed != null + ? (controller, detail) => onProcessFailed.call(controller, detail) + : null, + onAcceleratorKeyPressed: onAcceleratorKeyPressed != null + ? (controller, detail) => + onAcceleratorKeyPressed.call(controller, detail) + : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, )); ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView.run} diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart index b885aa3c1..6368630e7 100755 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart @@ -1,20 +1,19 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../webview_environment/webview_environment.dart'; -import 'headless_in_app_webview.dart'; -import 'in_app_webview_controller.dart'; + import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} class InAppWebView extends StatefulWidget { @@ -35,266 +34,141 @@ class InAppWebView extends StatefulWidget { final PlatformInAppWebViewWidget platform; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} - InAppWebView({ - Key? key, - Set>? gestureRecognizers, - int? windowId, - HeadlessInAppWebView? headlessWebView, - InAppWebViewKeepAlive? keepAlive, - bool? preventGestureDelay, - TextDirection? layoutDirection, - WebViewEnvironment? webViewEnvironment, - @Deprecated('Use onGeolocationPermissionsHidePrompt instead') - void Function(InAppWebViewController controller)? - androidOnGeolocationPermissionsHidePrompt, - @Deprecated('Use onGeolocationPermissionsShowPrompt instead') - Future Function( - InAppWebViewController controller, String origin)? - androidOnGeolocationPermissionsShowPrompt, - @Deprecated('Use onPermissionRequest instead') - Future Function( - InAppWebViewController controller, - String origin, - List resources)? - androidOnPermissionRequest, - @Deprecated('Use onSafeBrowsingHit instead') - Future Function(InAppWebViewController controller, - Uri url, SafeBrowsingThreat? threatType)? - androidOnSafeBrowsingHit, - InAppWebViewInitialData? initialData, - String? initialFile, - @Deprecated('Use initialSettings instead') - InAppWebViewGroupOptions? initialOptions, - InAppWebViewSettings? initialSettings, - URLRequest? initialUrlRequest, - UnmodifiableListView? initialUserScripts, - PullToRefreshController? pullToRefreshController, - FindInteractionController? findInteractionController, - ContextMenu? contextMenu, - void Function(InAppWebViewController controller, WebUri? url)? - onPageCommitVisible, - void Function(InAppWebViewController controller, String? title)? - onTitleChanged, - @Deprecated( - 'Use onDidReceiveServerRedirectForProvisionalNavigation instead') - void Function(InAppWebViewController controller)? - iosOnDidReceiveServerRedirectForProvisionalNavigation, - @Deprecated('Use onWebContentProcessDidTerminate instead') - void Function(InAppWebViewController controller)? - iosOnWebContentProcessDidTerminate, - @Deprecated('Use onNavigationResponse instead') - Future Function( - InAppWebViewController controller, - IOSWKNavigationResponse navigationResponse)? - iosOnNavigationResponse, - @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - iosShouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxProgress, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - onAjaxReadyStateChange, - void Function( - InAppWebViewController controller, ConsoleMessage consoleMessage)? - onConsoleMessage, - Future Function(InAppWebViewController controller, - CreateWindowAction createWindowAction)? - onCreateWindow, - void Function(InAppWebViewController controller)? onCloseWindow, - void Function(InAppWebViewController controller)? onWindowFocus, - void Function(InAppWebViewController controller)? onWindowBlur, - @Deprecated('Use onReceivedIcon instead') - void Function(InAppWebViewController controller, Uint8List icon)? - androidOnReceivedIcon, - @Deprecated('Use onReceivedTouchIconUrl instead') - void Function(InAppWebViewController controller, Uri url, bool precomposed)? - androidOnReceivedTouchIconUrl, - @Deprecated('Use onDownloadStartRequest instead') - void Function(InAppWebViewController controller, Uri url)? onDownloadStart, - void Function(InAppWebViewController controller, - DownloadStartRequest downloadStartRequest)? - onDownloadStartRequest, - @Deprecated('Use FindInteractionController.onFindResultReceived instead') - void Function(InAppWebViewController controller, int activeMatchOrdinal, - int numberOfMatches, bool isDoneCounting)? - onFindResultReceived, - Future Function( - InAppWebViewController controller, JsAlertRequest jsAlertRequest)? - onJsAlert, - Future Function(InAppWebViewController controller, - JsConfirmRequest jsConfirmRequest)? - onJsConfirm, - Future Function( - InAppWebViewController controller, JsPromptRequest jsPromptRequest)? - onJsPrompt, - @Deprecated("Use onReceivedError instead") - void Function(InAppWebViewController controller, Uri? url, int code, - String message)? - onLoadError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceError error)? - onReceivedError, - @Deprecated("Use onReceivedHttpError instead") - void Function(InAppWebViewController controller, Uri? url, int statusCode, - String description)? - onLoadHttpError, - void Function(InAppWebViewController controller, WebResourceRequest request, - WebResourceResponse errorResponse)? - onReceivedHttpError, - void Function(InAppWebViewController controller, LoadedResource resource)? - onLoadResource, - @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future Function( - InAppWebViewController controller, Uri url)? - onLoadResourceCustomScheme, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - onLoadResourceWithCustomScheme, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, - void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, - void Function(InAppWebViewController controller, - InAppWebViewHitTestResult hitTestResult)? - onLongPressHitTestResult, - @Deprecated("Use onPrintRequest instead") - void Function(InAppWebViewController controller, Uri? url)? onPrint, - Future Function(InAppWebViewController controller, WebUri? url, - PlatformPrintJobController? printJobController)? - onPrintRequest, - void Function(InAppWebViewController controller, int progress)? - onProgressChanged, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedClientCertRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedHttpAuthRequest, - Future Function(InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - onReceivedServerTrustAuthRequest, - void Function(InAppWebViewController controller, int x, int y)? - onScrollChanged, - void Function( - InAppWebViewController controller, WebUri? url, bool? isReload)? - onUpdateVisitedHistory, - void Function(InAppWebViewController controller)? onWebViewCreated, - Future Function( - InAppWebViewController controller, AjaxRequest ajaxRequest)? - shouldInterceptAjaxRequest, - Future Function( - InAppWebViewController controller, FetchRequest fetchRequest)? - shouldInterceptFetchRequest, - Future Function(InAppWebViewController controller, - NavigationAction navigationAction)? - shouldOverrideUrlLoading, - void Function(InAppWebViewController controller)? onEnterFullscreen, - void Function(InAppWebViewController controller)? onExitFullscreen, - void Function(InAppWebViewController controller, int x, int y, - bool clampedX, bool clampedY)? - onOverScrolled, - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - onZoomScaleChanged, - @Deprecated('Use shouldInterceptRequest instead') - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - androidShouldInterceptRequest, - @Deprecated('Use onRenderProcessUnresponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessUnresponsive, - @Deprecated('Use onRenderProcessResponsive instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnRenderProcessResponsive, - @Deprecated('Use onRenderProcessGone instead') - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - androidOnRenderProcessGone, - @Deprecated('Use onFormResubmission instead') - Future Function( - InAppWebViewController controller, Uri? url)? - androidOnFormResubmission, - @Deprecated('Use onZoomScaleChanged instead') - void Function(InAppWebViewController controller, double oldScale, - double newScale)? - androidOnScaleChanged, - @Deprecated('Use onJsBeforeUnload instead') - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - androidOnJsBeforeUnload, - @Deprecated('Use onReceivedLoginRequest instead') - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - androidOnReceivedLoginRequest, - void Function(InAppWebViewController controller)? - onDidReceiveServerRedirectForProvisionalNavigation, - Future Function( - InAppWebViewController controller, WebUri? url)? - onFormResubmission, - void Function(InAppWebViewController controller)? - onGeolocationPermissionsHidePrompt, - Future Function( - InAppWebViewController controller, String origin)? - onGeolocationPermissionsShowPrompt, - Future Function(InAppWebViewController controller, - JsBeforeUnloadRequest jsBeforeUnloadRequest)? - onJsBeforeUnload, - Future Function( - InAppWebViewController controller, - NavigationResponse navigationResponse)? - onNavigationResponse, - Future Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequest, - void Function(InAppWebViewController controller, Uint8List icon)? - onReceivedIcon, - void Function(InAppWebViewController controller, LoginRequest loginRequest)? - onReceivedLoginRequest, - void Function(InAppWebViewController controller, - PermissionRequest permissionRequest)? - onPermissionRequestCanceled, - void Function(InAppWebViewController controller)? onRequestFocus, - void Function( - InAppWebViewController controller, WebUri url, bool precomposed)? - onReceivedTouchIconUrl, - void Function( - InAppWebViewController controller, RenderProcessGoneDetail detail)? - onRenderProcessGone, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessResponsive, - Future Function( - InAppWebViewController controller, WebUri? url)? - onRenderProcessUnresponsive, - Future Function(InAppWebViewController controller, - WebUri url, SafeBrowsingThreat? threatType)? - onSafeBrowsingHit, - void Function(InAppWebViewController controller)? - onWebContentProcessDidTerminate, - Future Function( - InAppWebViewController controller, - URLAuthenticationChallenge challenge)? - shouldAllowDeprecatedTLS, - Future Function( - InAppWebViewController controller, WebResourceRequest request)? - shouldInterceptRequest, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onCameraCaptureStateChanged, - Future Function( - InAppWebViewController controller, - MediaCaptureState? oldState, - MediaCaptureState? newState, - )? onMicrophoneCaptureStateChanged, - void Function(InAppWebViewController controller, Size oldContentSize, - Size newContentSize)? - onContentSizeChanged, - }) : this.fromPlatformCreationParams( + InAppWebView( + {Key? key, + Set>? gestureRecognizers, + int? windowId, + HeadlessInAppWebView? headlessWebView, + InAppWebViewKeepAlive? keepAlive, + bool? preventGestureDelay, + TextDirection? layoutDirection, + WebViewEnvironment? webViewEnvironment, + @Deprecated('Use onGeolocationPermissionsHidePrompt instead') + void Function(InAppWebViewController controller)? + androidOnGeolocationPermissionsHidePrompt, + @Deprecated('Use onGeolocationPermissionsShowPrompt instead') + FutureOr Function( + InAppWebViewController controller, String origin)? + androidOnGeolocationPermissionsShowPrompt, + @Deprecated('Use onPermissionRequest instead') + FutureOr Function( + InAppWebViewController controller, + String origin, + List resources)? + androidOnPermissionRequest, + @Deprecated('Use onSafeBrowsingHit instead') + FutureOr Function( + InAppWebViewController controller, + Uri url, + SafeBrowsingThreat? threatType)? + androidOnSafeBrowsingHit, + InAppWebViewInitialData? initialData, + String? initialFile, + @Deprecated('Use initialSettings instead') + InAppWebViewGroupOptions? initialOptions, + InAppWebViewSettings? initialSettings, + URLRequest? initialUrlRequest, + UnmodifiableListView? initialUserScripts, + PullToRefreshController? pullToRefreshController, + FindInteractionController? findInteractionController, + ContextMenu? contextMenu, + void Function(InAppWebViewController controller, WebUri? url)? + onPageCommitVisible, + void Function(InAppWebViewController controller, String? title)? + onTitleChanged, + @Deprecated('Use onDidReceiveServerRedirectForProvisionalNavigation instead') + void Function(InAppWebViewController controller)? + iosOnDidReceiveServerRedirectForProvisionalNavigation, + @Deprecated('Use onWebContentProcessDidTerminate instead') + void Function(InAppWebViewController controller)? + iosOnWebContentProcessDidTerminate, + @Deprecated('Use onNavigationResponse instead') + FutureOr Function(InAppWebViewController controller, IOSWKNavigationResponse navigationResponse)? iosOnNavigationResponse, + @Deprecated('Use shouldAllowDeprecatedTLS instead') FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? iosShouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxProgress, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxReadyStateChange, + void Function(InAppWebViewController controller, ConsoleMessage consoleMessage)? onConsoleMessage, + FutureOr Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow, + void Function(InAppWebViewController controller)? onCloseWindow, + void Function(InAppWebViewController controller)? onWindowFocus, + void Function(InAppWebViewController controller)? onWindowBlur, + @Deprecated('Use onReceivedIcon instead') void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon, + @Deprecated('Use onReceivedTouchIconUrl instead') void Function(InAppWebViewController controller, Uri url, bool precomposed)? androidOnReceivedTouchIconUrl, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, Uri url)? onDownloadStart, + @Deprecated('Use onDownloadStarting instead') void Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStartRequest, + FutureOr Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStarting, + @Deprecated('Use FindInteractionController.onFindResultReceived instead') void Function(InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting)? onFindResultReceived, + FutureOr Function(InAppWebViewController controller, JsAlertRequest jsAlertRequest)? onJsAlert, + FutureOr Function(InAppWebViewController controller, JsConfirmRequest jsConfirmRequest)? onJsConfirm, + FutureOr Function(InAppWebViewController controller, JsPromptRequest jsPromptRequest)? onJsPrompt, + @Deprecated("Use onReceivedError instead") void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceError error)? onReceivedError, + @Deprecated("Use onReceivedHttpError instead") void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError, + void Function(InAppWebViewController controller, LoadedResource resource)? onLoadResource, + @Deprecated('Use onLoadResourceWithCustomScheme instead') FutureOr Function(InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStart, + void Function(InAppWebViewController controller, WebUri? url)? onLoadStop, + void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult, + @Deprecated("Use onPrintRequest instead") void Function(InAppWebViewController controller, Uri? url)? onPrint, + FutureOr Function(InAppWebViewController controller, WebUri? url, PlatformPrintJobController? printJobController)? onPrintRequest, + void Function(InAppWebViewController controller, int progress)? onProgressChanged, + FutureOr Function(InAppWebViewController controller, ClientCertChallenge challenge)? onReceivedClientCertRequest, + FutureOr Function(InAppWebViewController controller, HttpAuthenticationChallenge challenge)? onReceivedHttpAuthRequest, + FutureOr Function(InAppWebViewController controller, ServerTrustChallenge challenge)? onReceivedServerTrustAuthRequest, + void Function(InAppWebViewController controller, int x, int y)? onScrollChanged, + void Function(InAppWebViewController controller, WebUri? url, bool? isReload)? onUpdateVisitedHistory, + void Function(InAppWebViewController controller)? onWebViewCreated, + FutureOr Function(InAppWebViewController controller, AjaxRequest ajaxRequest)? shouldInterceptAjaxRequest, + FutureOr Function(InAppWebViewController controller, FetchRequest fetchRequest)? shouldInterceptFetchRequest, + FutureOr Function(InAppWebViewController controller, NavigationAction navigationAction)? shouldOverrideUrlLoading, + void Function(InAppWebViewController controller)? onEnterFullscreen, + void Function(InAppWebViewController controller)? onExitFullscreen, + void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled, + void Function(InAppWebViewController controller, double oldScale, double newScale)? onZoomScaleChanged, + @Deprecated('Use shouldInterceptRequest instead') FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? androidShouldInterceptRequest, + @Deprecated('Use onRenderProcessUnresponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessUnresponsive, + @Deprecated('Use onRenderProcessResponsive instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnRenderProcessResponsive, + @Deprecated('Use onRenderProcessGone instead') void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? androidOnRenderProcessGone, + @Deprecated('Use onFormResubmission instead') FutureOr Function(InAppWebViewController controller, Uri? url)? androidOnFormResubmission, + @Deprecated('Use onZoomScaleChanged instead') void Function(InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged, + @Deprecated('Use onJsBeforeUnload instead') FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? androidOnJsBeforeUnload, + @Deprecated('Use onReceivedLoginRequest instead') void Function(InAppWebViewController controller, LoginRequest loginRequest)? androidOnReceivedLoginRequest, + void Function(InAppWebViewController controller)? onDidReceiveServerRedirectForProvisionalNavigation, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onFormResubmission, + void Function(InAppWebViewController controller)? onGeolocationPermissionsHidePrompt, + FutureOr Function(InAppWebViewController controller, String origin)? onGeolocationPermissionsShowPrompt, + FutureOr Function(InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? onJsBeforeUnload, + FutureOr Function(InAppWebViewController controller, NavigationResponse navigationResponse)? onNavigationResponse, + FutureOr Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequest, + void Function(InAppWebViewController controller, Uint8List icon)? onReceivedIcon, + void Function(InAppWebViewController controller, LoginRequest loginRequest)? onReceivedLoginRequest, + void Function(InAppWebViewController controller, PermissionRequest permissionRequest)? onPermissionRequestCanceled, + void Function(InAppWebViewController controller)? onRequestFocus, + void Function(InAppWebViewController controller, WebUri url, bool precomposed)? onReceivedTouchIconUrl, + void Function(InAppWebViewController controller, RenderProcessGoneDetail detail)? onRenderProcessGone, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessResponsive, + FutureOr Function(InAppWebViewController controller, WebUri? url)? onRenderProcessUnresponsive, + FutureOr Function(InAppWebViewController controller, WebUri url, SafeBrowsingThreat? threatType)? onSafeBrowsingHit, + void Function(InAppWebViewController controller)? onWebContentProcessDidTerminate, + FutureOr Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? shouldAllowDeprecatedTLS, + FutureOr Function(InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged, + FutureOr Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged, + void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, + void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) + : this.fromPlatformCreationParams( key: key, params: PlatformInAppWebViewWidgetCreationParams( controllerFromPlatform: @@ -365,6 +239,10 @@ class InAppWebView extends StatefulWidget { ? (controller, downloadStartRequest) => onDownloadStartRequest .call(controller, downloadStartRequest) : null, + onDownloadStarting: onDownloadStarting != null + ? (controller, downloadStartRequest) => + onDownloadStarting.call(controller, downloadStartRequest) + : null, onLoadResourceCustomScheme: onLoadResourceCustomScheme != null ? (controller, url) => onLoadResourceCustomScheme.call(controller, url) @@ -656,6 +534,18 @@ class InAppWebView extends StatefulWidget { onContentSizeChanged.call( controller, oldContentSize, newContentSize) : null, + onProcessFailed: onProcessFailed != null + ? (controller, detail) => + onProcessFailed.call(controller, detail) + : null, + onAcceleratorKeyPressed: onAcceleratorKeyPressed != null + ? (controller, detail) => + onAcceleratorKeyPressed.call(controller, detail) + : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, gestureRecognizers: gestureRecognizers, headlessWebView: headlessWebView?.platform, preventGestureDelay: preventGestureDelay, diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart index 1544754d3..ef61cc4d0 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,18 +1,16 @@ import 'dart:core'; -import 'dart:typed_data'; -import 'dart:ui'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; + import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; +import '../print_job/main.dart'; import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; + import 'android/in_app_webview_controller.dart'; import 'apple/in_app_webview_controller.dart'; -import '../print_job/main.dart'; - ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController} class InAppWebViewController { ///Use [InAppWebViewController] instead. @@ -166,14 +164,12 @@ class InAppWebViewController { ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.addJavaScriptHandler} void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) => + {required String handlerName, required Function callback}) => platform.addJavaScriptHandler( handlerName: handlerName, callback: callback); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.removeJavaScriptHandler} - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) => + Function? removeJavaScriptHandler({required String handlerName}) => platform.removeJavaScriptHandler(handlerName: handlerName); ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.hasJavaScriptHandler} @@ -290,9 +286,26 @@ class InAppWebViewController { Future getHitTestResult() => platform.getHitTestResult(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.requestFocus} + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) => + platform.requestFocus( + direction: direction, previouslyFocusedRect: previouslyFocusedRect); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.clearFocus} Future clearFocus() => platform.clearFocus(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setInputMethodEnabled} + Future setInputMethodEnabled(bool enabled) => + platform.setInputMethodEnabled(enabled); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.showInputMethod} + Future showInputMethod() => platform.showInputMethod(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.hideInputMethod} + Future hideInputMethod() => platform.hideInputMethod(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setContextMenu} Future setContextMenu(ContextMenu? contextMenu) => platform.setContextMenu(contextMenu); @@ -505,6 +518,16 @@ class InAppWebViewController { {required String eventName}) => platform.removeDevToolsProtocolEventListener(eventName: eventName); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.isInterfaceSupported} + Future isInterfaceSupported(WebViewInterface interface) => + platform.isInterfaceSupported(interface); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.saveState} + Future saveState() => platform.saveState(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.restoreState} + Future restoreState(Uint8List state) => platform.restoreState(state); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getIFrameId} Future getIFrameId() => platform.getIFrameId(); @@ -565,6 +588,19 @@ class InAppWebViewController { PlatformInAppWebViewController.static() .clearAllCache(includeDiskFiles: includeDiskFiles); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.enableSlowWholeDocumentDraw} + static Future enableSlowWholeDocumentDraw() => + PlatformInAppWebViewController.static().enableSlowWholeDocumentDraw(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.setJavaScriptBridgeName} + static Future setJavaScriptBridgeName(String bridgeName) => + PlatformInAppWebViewController.static() + .setJavaScriptBridgeName(bridgeName); + + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.getJavaScriptBridgeName} + static Future getJavaScriptBridgeName() => + PlatformInAppWebViewController.static().getJavaScriptBridgeName(); + ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewController.tRexRunnerHtml} static Future get tRexRunnerHtml => PlatformInAppWebViewController.static().tRexRunnerHtml; diff --git a/flutter_inappwebview/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview/lib/src/print_job/print_job_controller.dart index 427d1e8cc..ded9521e0 100644 --- a/flutter_inappwebview/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview/lib/src/print_job/print_job_controller.dart @@ -3,10 +3,9 @@ import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_pla ///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} class PrintJobController { ///{@macro flutter_inappwebview_platform_interface.PlatformPrintJobController} - PrintJobController({required String id, PrintJobCompletionHandler onComplete}) + PrintJobController({required String id}) : this.fromPlatformCreationParams( - params: PlatformPrintJobControllerCreationParams( - id: id, onComplete: onComplete)); + params: PlatformPrintJobControllerCreationParams(id: id)); /// Constructs a [PrintJobController]. /// diff --git a/flutter_inappwebview/lib/src/web_message/web_message_port.dart b/flutter_inappwebview/lib/src/web_message/web_message_port.dart index 5d48b78e5..f9f5dba91 100644 --- a/flutter_inappwebview/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview/lib/src/web_message/web_message_port.dart @@ -31,7 +31,8 @@ class WebMessagePort implements IWebMessagePort { ///{@macro flutter_inappwebview_platform_interface.PlatformWebMessagePort.close} Future close() => platform.close(); - Map toMap() => platform.toMap(); + Map toMap({EnumMethod? enumMethod}) => + platform.toMap(enumMethod: enumMethod); Map toJson() => platform.toJson(); diff --git a/flutter_inappwebview/lib/src/webview_asset_loader.dart b/flutter_inappwebview/lib/src/webview_asset_loader.dart index 7ccc155ab..2e42d9e4c 100644 --- a/flutter_inappwebview/lib/src/webview_asset_loader.dart +++ b/flutter_inappwebview/lib/src/webview_asset_loader.dart @@ -26,7 +26,8 @@ abstract class PathHandler } @override - Map toMap() => platform.toMap(); + Map toMap({EnumMethod? enumMethod}) => + platform.toMap(enumMethod: enumMethod); @override Map toJson() => platform.toJson(); diff --git a/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart index eb77c9486..141804c37 100644 --- a/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart +++ b/flutter_inappwebview/lib/src/webview_environment/webview_environment.dart @@ -24,6 +24,18 @@ class WebViewEnvironment { ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.settings} WebViewEnvironmentSettings? get settings => platform.settings; + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isInterfaceSupported} + Future isInterfaceSupported(WebViewInterface interface) => + platform.isInterfaceSupported(interface); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + Future> getProcessInfos() => + platform.getProcessInfos(); + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getFailureReportFolderPath} + Future getFailureReportFolderPath() => + platform.getFailureReportFolderPath(); + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} static Future create( {WebViewEnvironmentSettings? settings}) async { @@ -44,6 +56,26 @@ class WebViewEnvironment { PlatformWebViewEnvironment.static() .compareBrowserVersions(version1: version1, version2: version2); + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onNewBrowserVersionAvailable} + void Function()? get onNewBrowserVersionAvailable => + platform.onNewBrowserVersionAvailable; + set onNewBrowserVersionAvailable(void Function()? value) => + platform.onNewBrowserVersionAvailable = value; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onBrowserProcessExited} + void Function(BrowserProcessExitedDetail detail)? + get onBrowserProcessExited => platform.onBrowserProcessExited; + set onBrowserProcessExited( + void Function(BrowserProcessExitedDetail detail)? value) => + platform.onBrowserProcessExited = value; + + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onProcessInfosChanged} + void Function(BrowserProcessInfosChangedDetail detail)? + get onProcessInfosChanged => platform.onProcessInfosChanged; + set onProcessInfosChanged( + void Function(BrowserProcessInfosChangedDetail detail)? value) => + platform.onProcessInfosChanged = value; + ///{@macro flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} Future dispose() => platform.dispose(); } diff --git a/flutter_inappwebview/pubspec.yaml b/flutter_inappwebview/pubspec.yaml index 35b7f83d5..15023b3da 100755 --- a/flutter_inappwebview/pubspec.yaml +++ b/flutter_inappwebview/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 6.1.4 +version: 6.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,12 +20,18 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.3.0 - flutter_inappwebview_android: ^1.1.3 - flutter_inappwebview_ios: ^1.1.2 - flutter_inappwebview_macos: ^1.1.2 - flutter_inappwebview_web: ^1.1.2 - flutter_inappwebview_windows: ^0.5.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface + flutter_inappwebview_android: #^1.2.0-beta.3 + path: ../flutter_inappwebview_android + flutter_inappwebview_ios: #^1.2.0-beta.3 + path: ../flutter_inappwebview_ios + flutter_inappwebview_macos: #^1.2.0-beta.3 + path: ../flutter_inappwebview_macos + flutter_inappwebview_web: #^1.2.0-beta.3 + path: ../flutter_inappwebview_web + flutter_inappwebview_windows: #^0.7.0-beta.3 + path: ../flutter_inappwebview_windows dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_android/CHANGELOG.md b/flutter_inappwebview_android/CHANGELOG.md index 3170f86a5..fb2d473f3 100644 --- a/flutter_inappwebview_android/CHANGELOG.md +++ b/flutter_inappwebview_android/CHANGELOG.md @@ -1,3 +1,38 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event +- Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `hideInputMethod`, `showInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "[Android] PrintJobOrientation _TypeError (type 'Null' is not a subtype of type 'int')" [#2413](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2413) +- Fixed "Accessibility Android" [#1694](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1694) +- Fixed "Automatic font scale according to accessibility option 'font size' of device does not work on Android" [#540](https://github.com/pichillilorenzo/flutter_inappwebview/issues/540) +- Fixed "callHandler method is not injected into InAppBrowser" [#1973](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1973) + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Added `InAppWebViewController.enableSlowWholeDocumentDraw` static method +- Added `CookieManager.flush` method +- Added support for `UserScript.forMainFrameOnly` parameter +- Implemented `requestFocus` WebView method +- Updated UserScript at document end implementation +- Updated `InAppWebViewController.takeScreenshot` implementation to support screenshot out of visible viewport when `InAppWebViewController.enableSlowWholeDocumentDraw` is called +- Fixed "After dispose a InAppWebViewKeepAlive using InAppWebViewController.disposeKeepAlive. NullPointerException is thrown when main activity enter destroyed state." [#2025](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2025) +- Fixed crash when trying to open InAppBrowser with R.menu.menu_main on release mode +- Fixed "android.webkit.WebSettingsWrapper cannot be cast to com.android.webview.chromium.ContentSettingsAdapter" [#2397](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2397) +- Merged "Prevent blank InAppBrowser Activity from being restored" [#1984](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1984) (thanks to [ShuheiSuzuki-07](https://github.com/ShuheiSuzuki-07)) +- Merged "Update Android Cookie Expiration date format to 24-hour format (HH)" [#2389](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2389) (thanks to [takuyaaaaaaahaaaaaa](https://github.com/takuyaaaaaaahaaaaaa)) +- Merged "[Android] allow sync navigation requests using a regular expression" [#2008](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2008) (thanks to [lyb5834](https://github.com/lyb5834)) + ## 1.1.3 - Updated flutter_inappwebview_platform_interface version to ^1.3.0 diff --git a/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java b/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java new file mode 100644 index 000000000..2d3178171 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/android/print/InAppWebViewPrintDocumentAdapter.java @@ -0,0 +1,123 @@ +package android.print; + +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class InAppWebViewPrintDocumentAdapter extends PrintDocumentAdapter { + @NonNull + private final PrintDocumentAdapter delegate; + @Nullable + private final PrintDocumentAdapterCallback callback; + + public InAppWebViewPrintDocumentAdapter(@NonNull PrintDocumentAdapter delegate, @Nullable PrintDocumentAdapterCallback callback) { + this.delegate = delegate; + this.callback = callback; + } + + @Override + public void onStart() { + this.delegate.onStart(); + if (this.callback != null) this.callback.onStart(); + } + + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback layoutResultCallback, Bundle extras) { + this.delegate.onLayout(oldAttributes, newAttributes, cancellationSignal, new LayoutResultCallback() { + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + layoutResultCallback.onLayoutFinished(info, changed); + if (callback != null) callback.onLayoutFinished(info, changed); + } + + @Override + public void onLayoutFailed(CharSequence error) { + layoutResultCallback.onLayoutFailed(error); + if (callback != null) callback.onLayoutFailed(error); + } + + @Override + public void onLayoutCancelled() { + layoutResultCallback.onLayoutCancelled(); + if (callback != null) callback.onLayoutCancelled(); + } + }, extras); + + if (callback != null) callback.onLayout(oldAttributes, newAttributes, cancellationSignal, layoutResultCallback, extras); + } + + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { + this.delegate.onWrite(pages, destination, cancellationSignal, new WriteResultCallback() { + @Override + public void onWriteFinished(PageRange[] pages) { + writeResultCallback.onWriteFinished(pages); + if (callback != null) callback.onWriteFinished(pages); + } + + @Override + public void onWriteFailed(CharSequence error) { + writeResultCallback.onWriteFailed(error); + if (callback != null) callback.onWriteFailed(error); + } + + @Override + public void onWriteCancelled() { + writeResultCallback.onWriteCancelled(); + if (callback != null) callback.onWriteCancelled(); + } + }); + if (callback != null) callback.onWrite(pages, destination, cancellationSignal, writeResultCallback); + } + + @Override + public void onFinish() { + this.delegate.onFinish(); + if (this.callback != null) this.callback.onFinish(); + } + + public static class PrintDocumentAdapterCallback { + public void onStart() { + /* do nothing - stub */ + } + + public void onFinish() { + /* do nothing - stub */ + } + + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback layoutResultCallback, Bundle extras) { + /* do nothing - stub */ + } + + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + /* do nothing - stub */ + } + + public void onLayoutFailed(CharSequence error) { + /* do nothing - stub */ + } + + public void onLayoutCancelled() { + /* do nothing - stub */ + } + + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { + /* do nothing - stub */ + } + + public void onWriteFinished(PageRange[] pages) { + /* do nothing - stub */ + } + + public void onWriteFailed(CharSequence error) { + /* do nothing - stub */ + } + + public void onWriteCancelled() { + /* do nothing - stub */ + } + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java index ff4247e7e..dccb7eabf 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/MyCookieManager.java @@ -103,6 +103,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case "removeSessionCookies": removeSessionCookies(result); break; + case "flush": + flush(result); + break; default: result.notImplemented(); } @@ -250,7 +253,7 @@ public List> getCookies(final String url) { if (cookieParamName.equalsIgnoreCase("Expires")) { try { - final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US); + final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); Date expiryDate = sdf.parse(cookieParamValue); if (expiryDate != null) { cookieMap.put("expiresDate", expiryDate.getTime()); @@ -423,8 +426,22 @@ else if (plugin != null) { } } + public void flush(MethodChannel.Result result) { + cookieManager = getCookieManager(); + if (cookieManager == null) { + result.success(false); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cookieManager.flush(); + } else if (plugin != null) { + CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext); + cookieSyncMngr.sync(); + } + } + public static String getCookieExpirationDate(Long timestamp) { - final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US); + final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); return sdf.format(new Date(timestamp)); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java index 444bc25d5..aae335ebb 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/chrome_custom_tabs/ChromeCustomTabsActivity.java @@ -78,13 +78,23 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.chrome_custom_tabs_layout); Bundle b = getIntent().getExtras(); - if (b == null) return; + if (b == null) { + if (savedInstanceState != null) { + close(); + } + return; + } id = b.getString("id"); String managerId = b.getString("managerId"); manager = ChromeSafariBrowserManager.shared.get(managerId); - if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return; + if (manager == null || manager.plugin == null || manager.plugin.messenger == null) { + if (savedInstanceState != null) { + close(); + } + return; + } manager.browsers.put(id, this); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java index 8564dbe82..e4486848a 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/content_blocker/ContentBlockerHandler.java @@ -157,9 +157,9 @@ public void run() { final String cssSelector = action.getSelector(); final String jsScript = "(function(d) { " + " function hide () { " + - " if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style')) { " + + " if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "-css-display-none-style')) { " + " var c = d.createElement('style'); " + - " c.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style'; " + + " c.id = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "-css-display-none-style'; " + " c.innerHTML = '" + cssSelector + " { display: none !important; }'; " + " d.body.appendChild(c); " + " }" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java index 6cb763c49..8c5ba07ae 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/in_app_browser/InAppBrowserActivity.java @@ -51,7 +51,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrowserDelegate, Disposable { protected static final String LOG_TAG = "InAppBrowserActivity"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_"; - + @Nullable public Integer windowId; public String id; @@ -77,19 +77,29 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow @Nullable public InAppBrowserChannelDelegate channelDelegate; public List menuItems = new ArrayList<>(); - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle b = getIntent().getExtras(); - if (b == null) return; - + if (b == null) { + if (savedInstanceState != null) { + finish(); + } + return; + } + id = b.getString("id"); String managerId = b.getString("managerId"); manager = InAppBrowserManager.shared.get(managerId); - if (manager == null || manager.plugin == null|| manager.plugin.messenger == null) return; + if (manager == null || manager.plugin == null || manager.plugin.messenger == null) { + if (savedInstanceState != null) { + finish(); + } + return; + } Map settingsMap = (Map) b.getSerializable("settings"); customSettings.parse(settingsMap); @@ -106,10 +116,12 @@ protected void onCreate(Bundle savedInstanceState) { pullToRefreshLayout.channelDelegate = new PullToRefreshChannelDelegate(pullToRefreshLayout, pullToRefreshLayoutChannel); pullToRefreshLayout.settings = pullToRefreshSettings; pullToRefreshLayout.prepare(); - + webView = findViewById(R.id.webView); webView.id = id; - webView.windowId = windowId; + if (windowId != -1) { + webView.windowId = windowId; + } webView.inAppBrowserDelegate = this; webView.plugin = manager.plugin; @@ -166,15 +178,13 @@ protected void onCreate(Bundle savedInstanceState) { Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e); return; } - } - else if (initialData != null) { + } else if (initialData != null) { String mimeType = b.getString("initialMimeType"); String encoding = b.getString("initialEncoding"); String baseUrl = b.getString("initialBaseUrl"); String historyUrl = b.getString("initialHistoryUrl"); webView.loadDataWithBaseURL(baseUrl, initialData, mimeType, encoding, historyUrl); - } - else if (initialUrlRequest != null) { + } else if (initialUrlRequest != null) { URLRequest urlRequest = URLRequest.fromMap(initialUrlRequest); if (urlRequest != null) { webView.loadUrl(urlRequest); @@ -237,8 +247,15 @@ public boolean onCreateOptionsMenu(Menu m) { } MenuInflater inflater = getMenuInflater(); - // Inflate menu to add items to action bar if it is present. - inflater.inflate(R.menu.menu_main, menu); + try { + // Inflate menu to add items to action bar if it is present. + inflater.inflate(R.menu.menu_main, menu); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, "Cannot inflate com.pichillilorenzo.flutter_inappwebview_android.R.menu.menu_main." + + "To make it work, you need to set minifyEnabled false and shrinkResources false in your build.gradle file."); + return super.onCreateOptionsMenu(m); + } MenuItem menuSearchItem = menu.findItem(R.id.menu_search); if (menuSearchItem != null) { @@ -526,8 +543,8 @@ public void setSettings(InAppBrowserSettings newSettings, HashMap getCustomSettings() { - Map webViewSettingsMap = webView != null ? webView.getCustomSettings() : null; + public Map getCustomSettingsMap() { + Map webViewSettingsMap = webView != null ? webView.getCustomSettingsMap() : null; if (customSettings == null || webViewSettingsMap == null) return null; @@ -602,9 +619,9 @@ public List getActivityResultListeners() { } @Override - protected void onActivityResult (int requestCode, - int resultCode, - Intent data) { + protected void onActivityResult(int requestCode, + int resultCode, + Intent data) { for (ActivityResultListener listener : activityResultListeners) { if (listener.onActivityResult(requestCode, resultCode, data)) { return; diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java index 2ec2e0178..8ac66dcb4 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptAjaxRequestJS.java @@ -1,266 +1,303 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class InterceptAjaxRequestJS { public static final String INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptAjaxRequest"; - public static final String FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._interceptOnlyAsyncAjaxRequests"; - public static final PluginScript INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript( - InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); + + public static String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useShouldInterceptAjaxRequest"; + } + + public static String FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnAjaxReadyStateChange"; + } + + public static String FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnAjaxProgress"; + } + + public static String FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._interceptOnlyAsyncAjaxRequests"; + } + + public static PluginScript INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly, + boolean initialUseOnAjaxReadyStateChange, + boolean initialUseOnAjaxProgress) { + return + new PluginScript( + InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } public static PluginScript createInterceptOnlyAsyncAjaxRequestsPluginScript(boolean onlyAsync) { return new PluginScript( InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - "window." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " = " + onlyAsync +";", + "window." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() + " = " + onlyAsync + ";", UserScriptInjectionTime.AT_DOCUMENT_START, null, true, - null + null, + false ); } - public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " = true;" + - " var send = ajax.prototype.send;" + - " var open = ajax.prototype.open;" + - " var setRequestHeader = ajax.prototype.setRequestHeader;" + - " ajax.prototype._flutter_inappwebview_url = null;" + - " ajax.prototype._flutter_inappwebview_method = null;" + - " ajax.prototype._flutter_inappwebview_isAsync = null;" + - " ajax.prototype._flutter_inappwebview_user = null;" + - " ajax.prototype._flutter_inappwebview_password = null;" + - " ajax.prototype._flutter_inappwebview_password = null;" + - " ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;" + - " ajax.prototype._flutter_inappwebview_request_headers = {};" + - " function convertRequestResponse(request, callback) {" + - " if (request.response != null && request.responseType != null) {" + - " switch (request.responseType) {" + - " case 'arraybuffer':" + - " callback(new Uint8Array(request.response));" + - " return;" + - " case 'blob':" + - " const reader = new FileReader();" + - " reader.addEventListener('loadend', function() { " + - " callback(new Uint8Array(reader.result));" + - " });" + - " reader.readAsArrayBuffer(blob);" + - " return;" + - " case 'document':" + - " callback(request.response.documentElement.outerHTML);" + - " return;" + - " case 'json':" + - " callback(request.response);" + - " return;" + - " };" + - " }" + - " callback(null);" + - " };" + - " ajax.prototype.open = function(method, url, isAsync, user, password) {" + - " isAsync = (isAsync != null) ? isAsync : true;" + - " this._flutter_inappwebview_url = url;" + - " this._flutter_inappwebview_method = method;" + - " this._flutter_inappwebview_isAsync = isAsync;" + - " this._flutter_inappwebview_user = user;" + - " this._flutter_inappwebview_password = password;" + - " this._flutter_inappwebview_request_headers = {};" + - " open.call(this, method, url, isAsync, user, password);" + - " };" + - " ajax.prototype.setRequestHeader = function(header, value) {" + - " this._flutter_inappwebview_request_headers[header] = value;" + - " setRequestHeader.call(this, header, value);" + - " };" + - " function handleEvent(e) {" + - " var self = this;" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true) {" + - " var headers = this.getAllResponseHeaders();" + - " var responseHeaders = {};" + - " if (headers != null) {" + - " var arr = headers.trim().split(/[\\r\\n]+/);" + - " arr.forEach(function (line) {" + - " var parts = line.split(': ');" + - " var header = parts.shift();" + - " var value = parts.join(': ');" + - " responseHeaders[header] = value;" + - " });" + - " }" + - " convertRequestResponse(this, function(response) {" + - " var ajaxRequest = {" + - " method: self._flutter_inappwebview_method," + - " url: self._flutter_inappwebview_url," + - " isAsync: self._flutter_inappwebview_isAsync," + - " user: self._flutter_inappwebview_user," + - " password: self._flutter_inappwebview_password," + - " withCredentials: self.withCredentials," + - " headers: self._flutter_inappwebview_request_headers," + - " readyState: self.readyState," + - " status: self.status," + - " responseURL: self.responseURL," + - " responseType: self.responseType," + - " response: response," + - " responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," + - " responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," + - " statusText: self.statusText," + - " responseHeaders, responseHeaders," + - " event: {" + - " type: e.type," + - " loaded: e.loaded," + - " lengthComputable: e.lengthComputable," + - " total: e.total" + - " }" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxProgress', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " }" + - " });" + - " });" + - " }" + - " };" + - " ajax.prototype.send = function(data) {" + - " var self = this;" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " var canBeIntercepted = self._flutter_inappwebview_isAsync || w." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " === false;" + - " if (canBeIntercepted && (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true)) {" + - " if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {" + - " this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" + - " var onreadystatechange = this.onreadystatechange;" + - " this.onreadystatechange = function() {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true) {" + - " var headers = this.getAllResponseHeaders();" + - " var responseHeaders = {};" + - " if (headers != null) {" + - " var arr = headers.trim().split(/[\\r\\n]+/);" + - " arr.forEach(function (line) {" + - " var parts = line.split(': ');" + - " var header = parts.shift();" + - " var value = parts.join(': ');" + - " responseHeaders[header] = value;" + - " });" + - " }" + - " convertRequestResponse(this, function(response) {" + - " var ajaxRequest = {" + - " method: self._flutter_inappwebview_method," + - " url: self._flutter_inappwebview_url," + - " isAsync: self._flutter_inappwebview_isAsync," + - " user: self._flutter_inappwebview_user," + - " password: self._flutter_inappwebview_password," + - " withCredentials: self.withCredentials," + - " headers: self._flutter_inappwebview_request_headers," + - " readyState: self.readyState," + - " status: self.status," + - " responseURL: self.responseURL," + - " responseType: self.responseType," + - " response: response," + - " responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," + - " responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," + - " statusText: self.statusText," + - " responseHeaders: responseHeaders" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " }" + - " if (onreadystatechange != null) {" + - " onreadystatechange();" + - " }" + - " });" + - " });" + - " } else if (onreadystatechange != null) {" + - " onreadystatechange();" + - " }" + - " };" + - " }" + - " this.addEventListener('loadstart', handleEvent);" + - " this.addEventListener('load', handleEvent);" + - " this.addEventListener('loadend', handleEvent);" + - " this.addEventListener('progress', handleEvent);" + - " this.addEventListener('error', handleEvent);" + - " this.addEventListener('abort', handleEvent);" + - " this.addEventListener('timeout', handleEvent);" + - " " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(data).then(function(data) {" + - " var ajaxRequest = {" + - " data: data," + - " method: self._flutter_inappwebview_method," + - " url: self._flutter_inappwebview_url," + - " isAsync: self._flutter_inappwebview_isAsync," + - " user: self._flutter_inappwebview_user," + - " password: self._flutter_inappwebview_password," + - " withCredentials: self.withCredentials," + - " headers: self._flutter_inappwebview_request_headers," + - " responseType: self.responseType" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) && result.data.length > 0) {" + - " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.data);" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + - " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" + - " if (result.headers != null) {" + - " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + - " } else {" + - " result.headers = { 'Content-Type': formDataContentType };" + - " }" + - " }" + - " }" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) || result.data == null) {" + - " data = result.data;" + - " } else if (result.data.length > 0) {" + - " data = new Uint8Array(result.data);" + - " }" + - " self.withCredentials = result.withCredentials;" + - " if (result.responseType != null && self._flutter_inappwebview_isAsync) {" + - " self.responseType = result.responseType;" + - " };" + - " for (var header in result.headers) {" + - " var value = result.headers[header];" + - " var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];" + - " if (flutter_inappwebview_value == null) {" + - " self._flutter_inappwebview_request_headers[header] = value;" + - " } else {" + - " self._flutter_inappwebview_request_headers[header] += ', ' + value;" + - " }" + - " setRequestHeader.call(self, header, value);" + - " };" + - " if ((self._flutter_inappwebview_method != result.method && result.method != null) ||" + - " (self._flutter_inappwebview_url != result.url && result.url != null) ||" + - " (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) ||" + - " (self._flutter_inappwebview_user != result.user && result.user != null) ||" + - " (self._flutter_inappwebview_password != result.password && result.password != null)) {" + - " self.abort();" + - " self.open(result.method, result.url, result.isAsync, result.user, result.password);" + - " }" + - " }" + - " send.call(self, data);" + - " });" + - " });" + - " } else {" + - " send.call(this, data);" + - " }" + - " };" + - "})(window.XMLHttpRequest);"; + public static String INTERCEPT_AJAX_REQUEST_JS_SOURCE(boolean initialUseOnAjaxReadyStateChange, boolean initialUseOnAjaxProgress) { + return + "(function(ajax) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " = true;" + + " w." + FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() + " = " + initialUseOnAjaxReadyStateChange + ";" + + " w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " = " + initialUseOnAjaxProgress + ";" + + " var send = ajax.prototype.send;" + + " var open = ajax.prototype.open;" + + " var setRequestHeader = ajax.prototype.setRequestHeader;" + + " ajax.prototype._flutter_inappwebview_url = null;" + + " ajax.prototype._flutter_inappwebview_method = null;" + + " ajax.prototype._flutter_inappwebview_isAsync = null;" + + " ajax.prototype._flutter_inappwebview_user = null;" + + " ajax.prototype._flutter_inappwebview_password = null;" + + " ajax.prototype._flutter_inappwebview_password = null;" + + " ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;" + + " ajax.prototype._flutter_inappwebview_request_headers = {};" + + " function convertRequestResponse(request, callback) {" + + " if (request.response != null && request.responseType != null) {" + + " switch (request.responseType) {" + + " case 'arraybuffer':" + + " callback(new Uint8Array(request.response));" + + " return;" + + " case 'blob':" + + " const reader = new FileReader();" + + " reader.addEventListener('loadend', function() { " + + " callback(new Uint8Array(reader.result));" + + " });" + + " reader.readAsArrayBuffer(blob);" + + " return;" + + " case 'document':" + + " callback(request.response.documentElement.outerHTML);" + + " return;" + + " case 'json':" + + " callback(request.response);" + + " return;" + + " };" + + " }" + + " callback(null);" + + " };" + + " ajax.prototype.open = function(method, url, isAsync, user, password) {" + + " isAsync = (isAsync != null) ? isAsync : true;" + + " this._flutter_inappwebview_url = url;" + + " this._flutter_inappwebview_method = method;" + + " this._flutter_inappwebview_isAsync = isAsync;" + + " this._flutter_inappwebview_user = user;" + + " this._flutter_inappwebview_password = password;" + + " this._flutter_inappwebview_request_headers = {};" + + " open.call(this, method, url, isAsync, user, password);" + + " };" + + " ajax.prototype.setRequestHeader = function(header, value) {" + + " this._flutter_inappwebview_request_headers[header] = value;" + + " setRequestHeader.call(this, header, value);" + + " };" + + " function handleEvent(e) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " === false || w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " == null || w." + FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() + " === false) {" + + " return;" + + " }" + + " var self = this;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == true) {" + + " var headers = this.getAllResponseHeaders();" + + " var responseHeaders = {};" + + " if (headers != null) {" + + " var arr = headers.trim().split(/[\\r\\n]+/);" + + " arr.forEach(function (line) {" + + " var parts = line.split(': ');" + + " var header = parts.shift();" + + " var value = parts.join(': ');" + + " responseHeaders[header] = value;" + + " });" + + " }" + + " convertRequestResponse(this, function(response) {" + + " var ajaxRequest = {" + + " method: self._flutter_inappwebview_method," + + " url: self._flutter_inappwebview_url," + + " isAsync: self._flutter_inappwebview_isAsync," + + " user: self._flutter_inappwebview_user," + + " password: self._flutter_inappwebview_password," + + " withCredentials: self.withCredentials," + + " headers: self._flutter_inappwebview_request_headers," + + " readyState: self.readyState," + + " status: self.status," + + " responseURL: self.responseURL," + + " responseType: self.responseType," + + " response: response," + + " responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," + + " responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," + + " statusText: self.statusText," + + " responseHeaders, responseHeaders," + + " event: {" + + " type: e.type," + + " loaded: e.loaded," + + " lengthComputable: e.lengthComputable," + + " total: e.total" + + " }" + + " };" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onAjaxProgress', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " }" + + " });" + + " });" + + " }" + + " };" + + " ajax.prototype.send = function(data) {" + + " var self = this;" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " var canBeIntercepted = self._flutter_inappwebview_isAsync || w." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() + " === false;" + + " if (canBeIntercepted && (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == true)) {" + + " if (w." + FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() + " === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) {" + + " this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" + + " var realOnreadystatechange = this.onreadystatechange;" + + " this.onreadystatechange = function() {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() + " == true) {" + + " var headers = this.getAllResponseHeaders();" + + " var responseHeaders = {};" + + " if (headers != null) {" + + " var arr = headers.trim().split(/[\\r\\n]+/);" + + " arr.forEach(function (line) {" + + " var parts = line.split(': ');" + + " var header = parts.shift();" + + " var value = parts.join(': ');" + + " responseHeaders[header] = value;" + + " });" + + " }" + + " convertRequestResponse(this, function(response) {" + + " var ajaxRequest = {" + + " method: self._flutter_inappwebview_method," + + " url: self._flutter_inappwebview_url," + + " isAsync: self._flutter_inappwebview_isAsync," + + " user: self._flutter_inappwebview_user," + + " password: self._flutter_inappwebview_password," + + " withCredentials: self.withCredentials," + + " headers: self._flutter_inappwebview_request_headers," + + " readyState: self.readyState," + + " status: self.status," + + " responseURL: self.responseURL," + + " responseType: self.responseType," + + " response: response," + + " responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," + + " responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," + + " statusText: self.statusText," + + " responseHeaders: responseHeaders" + + " };" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " }" + + " if (realOnreadystatechange != null) {" + + " realOnreadystatechange();" + + " }" + + " });" + + " });" + + " } else if (realOnreadystatechange != null) {" + + " realOnreadystatechange();" + + " }" + + " };" + + " }" + + " this.addEventListener('loadstart', handleEvent);" + + " this.addEventListener('load', handleEvent);" + + " this.addEventListener('loadend', handleEvent);" + + " this.addEventListener('progress', handleEvent);" + + " this.addEventListener('error', handleEvent);" + + " this.addEventListener('abort', handleEvent);" + + " this.addEventListener('timeout', handleEvent);" + + " " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyRequest(data).then(function(data) {" + + " var ajaxRequest = {" + + " data: data," + + " method: self._flutter_inappwebview_method," + + " url: self._flutter_inappwebview_url," + + " isAsync: self._flutter_inappwebview_isAsync," + + " user: self._flutter_inappwebview_user," + + " password: self._flutter_inappwebview_password," + + " withCredentials: self.withCredentials," + + " headers: self._flutter_inappwebview_request_headers," + + " responseType: self.responseType" + + " };" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.data) && result.data.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".arrayBufferToString(result.data);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".getFormDataContentType(bodyString);" + + " if (result.headers != null) {" + + " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + + " } else {" + + " result.headers = { 'Content-Type': formDataContentType };" + + " }" + + " }" + + " }" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.data) || result.data == null) {" + + " data = result.data;" + + " } else if (result.data.length > 0) {" + + " data = new Uint8Array(result.data);" + + " }" + + " self.withCredentials = result.withCredentials;" + + " if (result.responseType != null && self._flutter_inappwebview_isAsync) {" + + " self.responseType = result.responseType;" + + " };" + + " for (var header in result.headers) {" + + " var value = result.headers[header];" + + " var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];" + + " if (flutter_inappwebview_value == null) {" + + " self._flutter_inappwebview_request_headers[header] = value;" + + " } else {" + + " self._flutter_inappwebview_request_headers[header] += ', ' + value;" + + " }" + + " setRequestHeader.call(self, header, value);" + + " };" + + " if ((self._flutter_inappwebview_method != result.method && result.method != null) ||" + + " (self._flutter_inappwebview_url != result.url && result.url != null) ||" + + " (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) ||" + + " (self._flutter_inappwebview_user != result.user && result.user != null) ||" + + " (self._flutter_inappwebview_password != result.password && result.password != null)) {" + + " self.abort();" + + " self.open(result.method, result.url, result.isAsync, result.user, result.password);" + + " }" + + " }" + + " send.call(self, data);" + + " });" + + " });" + + " } else {" + + " send.call(this, data);" + + " }" + + " };" + + "})(window.XMLHttpRequest);"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java index eebff12cb..454717985 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/InterceptFetchRequestJS.java @@ -1,152 +1,166 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class InterceptFetchRequestJS { public static final String INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptFetchRequest"; - public static final PluginScript INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript( - InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); - public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " = true;" + - " if (fetch == null) {" + - " return;" + - " }" + - " window.fetch = async function(resource, init) {" + - " var w = (window.top == null || window.top === window) ? window : window.top;" + - " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == true) {" + - " var fetchRequest = {" + - " url: null," + - " method: null," + - " headers: null," + - " body: null," + - " mode: null," + - " credentials: null," + - " cache: null," + - " redirect: null," + - " referrer: null," + - " referrerPolicy: null," + - " integrity: null," + - " keepalive: null" + - " };" + - " if (resource instanceof Request) {" + - " fetchRequest.url = resource.url;" + - " fetchRequest.method = resource.method;" + - " fetchRequest.headers = resource.headers;" + - " fetchRequest.body = resource.body;" + - " fetchRequest.mode = resource.mode;" + - " fetchRequest.credentials = resource.credentials;" + - " fetchRequest.cache = resource.cache;" + - " fetchRequest.redirect = resource.redirect;" + - " fetchRequest.referrer = resource.referrer;" + - " fetchRequest.referrerPolicy = resource.referrerPolicy;" + - " fetchRequest.integrity = resource.integrity;" + - " fetchRequest.keepalive = resource.keepalive;" + - " } else {" + - " fetchRequest.url = resource != null ? resource.toString() : null;" + - " if (init != null) {" + - " fetchRequest.method = init.method;" + - " fetchRequest.headers = init.headers;" + - " fetchRequest.body = init.body;" + - " fetchRequest.mode = init.mode;" + - " fetchRequest.credentials = init.credentials;" + - " fetchRequest.cache = init.cache;" + - " fetchRequest.redirect = init.redirect;" + - " fetchRequest.referrer = init.referrer;" + - " fetchRequest.referrerPolicy = init.referrerPolicy;" + - " fetchRequest.integrity = init.integrity;" + - " fetchRequest.keepalive = init.keepalive;" + - " }" + - " }" + - " if (fetchRequest.headers instanceof Headers) {" + - " fetchRequest.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertHeadersToJson(fetchRequest.headers);" + - " }" + - " fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertCredentialsToJson(fetchRequest.credentials);" + - " return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(fetchRequest.body).then(function(body) {" + - " fetchRequest.body = body;" + - " return window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result.action) {" + - " case 0:" + - " var controller = new AbortController();" + - " if (init != null) {" + - " init.signal = controller.signal;" + - " } else {" + - " init = {" + - " signal: controller.signal" + - " };" + - " }" + - " controller.abort();" + - " break;" + - " }" + - " if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) && result.body.length > 0) {" + - " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.body);" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + - " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" + - " if (result.headers != null) {" + - " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + - " } else {" + - " result.headers = { 'Content-Type': formDataContentType };" + - " }" + - " }" + - " }" + - " resource = result.url;" + - " if (init == null) {" + - " init = {};" + - " }" + - " if (result.method != null && result.method.length > 0) {" + - " init.method = result.method;" + - " }" + - " if (result.headers != null && Object.keys(result.headers).length > 0) {" + - " init.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToHeaders(result.headers);" + - " }" + - " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) || result.body == null) {" + - " init.body = result.body;" + - " } else if (result.body.length > 0) {" + - " init.body = new Uint8Array(result.body);" + - " }" + - " if (result.mode != null && result.mode.length > 0) {" + - " init.mode = result.mode;" + - " }" + - " if (result.credentials != null) {" + - " init.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToCredential(result.credentials);" + - " }" + - " if (result.cache != null && result.cache.length > 0) {" + - " init.cache = result.cache;" + - " }" + - " if (result.redirect != null && result.redirect.length > 0) {" + - " init.redirect = result.redirect;" + - " }" + - " if (result.referrer != null && result.referrer.length > 0) {" + - " init.referrer = result.referrer;" + - " }" + - " if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {" + - " init.referrerPolicy = result.referrerPolicy;" + - " }" + - " if (result.integrity != null && result.integrity.length > 0) {" + - " init.integrity = result.integrity;" + - " }" + - " if (result.keepalive != null) {" + - " init.keepalive = result.keepalive;" + - " }" + - " return fetch(resource, init);" + - " }" + - " return fetch(resource, init);" + - " });" + - " });" + - " } else {" + - " return fetch(resource, init);" + - " }" + - " };" + - "})(window.fetch);"; + public static String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + return JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useShouldInterceptFetchRequest"; + } + + public static PluginScript INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return new PluginScript( + InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } + + public static String INTERCEPT_FETCH_REQUEST_JS_SOURCE() { + return "(function(fetch) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " = true;" + + " if (fetch == null) {" + + " return;" + + " }" + + " window.fetch = async function(resource, init) {" + + " var w = (window.top == null || window.top === window) ? window : window.top;" + + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() + " == true) {" + + " var fetchRequest = {" + + " url: null," + + " method: null," + + " headers: null," + + " body: null," + + " mode: null," + + " credentials: null," + + " cache: null," + + " redirect: null," + + " referrer: null," + + " referrerPolicy: null," + + " integrity: null," + + " keepalive: null" + + " };" + + " if (resource instanceof Request) {" + + " fetchRequest.url = resource.url;" + + " fetchRequest.method = resource.method;" + + " fetchRequest.headers = resource.headers;" + + " fetchRequest.body = resource.body;" + + " fetchRequest.mode = resource.mode;" + + " fetchRequest.credentials = resource.credentials;" + + " fetchRequest.cache = resource.cache;" + + " fetchRequest.redirect = resource.redirect;" + + " fetchRequest.referrer = resource.referrer;" + + " fetchRequest.referrerPolicy = resource.referrerPolicy;" + + " fetchRequest.integrity = resource.integrity;" + + " fetchRequest.keepalive = resource.keepalive;" + + " } else {" + + " fetchRequest.url = resource != null ? resource.toString() : null;" + + " if (init != null) {" + + " fetchRequest.method = init.method;" + + " fetchRequest.headers = init.headers;" + + " fetchRequest.body = init.body;" + + " fetchRequest.mode = init.mode;" + + " fetchRequest.credentials = init.credentials;" + + " fetchRequest.cache = init.cache;" + + " fetchRequest.redirect = init.redirect;" + + " fetchRequest.referrer = init.referrer;" + + " fetchRequest.referrerPolicy = init.referrerPolicy;" + + " fetchRequest.integrity = init.integrity;" + + " fetchRequest.keepalive = init.keepalive;" + + " }" + + " }" + + " if (fetchRequest.headers instanceof Headers) {" + + " fetchRequest.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertHeadersToJson(fetchRequest.headers);" + + " }" + + " fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertCredentialsToJson(fetchRequest.credentials);" + + " return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyRequest(fetchRequest.body).then(function(body) {" + + " fetchRequest.body = body;" + + " return window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result.action) {" + + " case 0:" + + " var controller = new AbortController();" + + " if (init != null) {" + + " init.signal = controller.signal;" + + " } else {" + + " init = {" + + " signal: controller.signal" + + " };" + + " }" + + " controller.abort();" + + " break;" + + " }" + + " if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.body) && result.body.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".arrayBufferToString(result.body);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".getFormDataContentType(bodyString);" + + " if (result.headers != null) {" + + " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + + " } else {" + + " result.headers = { 'Content-Type': formDataContentType };" + + " }" + + " }" + + " }" + + " resource = result.url;" + + " if (init == null) {" + + " init = {};" + + " }" + + " if (result.method != null && result.method.length > 0) {" + + " init.method = result.method;" + + " }" + + " if (result.headers != null && Object.keys(result.headers).length > 0) {" + + " init.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertJsonToHeaders(result.headers);" + + " }" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".isString(result.body) || result.body == null) {" + + " init.body = result.body;" + + " } else if (result.body.length > 0) {" + + " init.body = new Uint8Array(result.body);" + + " }" + + " if (result.mode != null && result.mode.length > 0) {" + + " init.mode = result.mode;" + + " }" + + " if (result.credentials != null) {" + + " init.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME() + ".convertJsonToCredential(result.credentials);" + + " }" + + " if (result.cache != null && result.cache.length > 0) {" + + " init.cache = result.cache;" + + " }" + + " if (result.redirect != null && result.redirect.length > 0) {" + + " init.redirect = result.redirect;" + + " }" + + " if (result.referrer != null && result.referrer.length > 0) {" + + " init.referrer = result.referrer;" + + " }" + + " if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {" + + " init.referrerPolicy = result.referrerPolicy;" + + " }" + + " if (result.integrity != null && result.integrity.length > 0) {" + + " init.integrity = result.integrity;" + + " }" + + " if (result.keepalive != null) {" + + " init.keepalive = result.keepalive;" + + " }" + + " return fetch(resource, init);" + + " }" + + " return fetch(resource, init);" + + " });" + + " });" + + " } else {" + + " return fetch(resource, init);" + + " }" + + " };" + + "})(window.fetch);"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java index 55659d479..a48d08a1e 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/JavaScriptBridgeJS.java @@ -1,250 +1,361 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.pichillilorenzo.flutter_inappwebview_android.Util; import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class JavaScriptBridgeJS { - public static final String JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + @NonNull + private static String _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + + public static void set_JAVASCRIPT_BRIDGE_NAME(@NonNull String bridgeName) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName; + } + + @NonNull + public static String get_JAVASCRIPT_BRIDGE_NAME() { + return _JAVASCRIPT_BRIDGE_NAME; + } + public static final String JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; - public static final PluginScript JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = new PluginScript( - JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); - public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util"; - public static final String WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._webMessageChannels"; + private static final String VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET"; + + public static PluginScript JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(@NonNull String expectedBridgeSecret, + @Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + String source = Util.replaceAll(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE(), VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, expectedBridgeSecret); + return new PluginScript( + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source, + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } + + public static String JAVASCRIPT_UTIL_VAR_NAME() { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + "._Util"; + } + + public static String WEB_MESSAGE_CHANNELS_VARIABLE_NAME() { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + "._webMessageChannels"; + } - public static final String UTIL_JS_SOURCE = JAVASCRIPT_UTIL_VAR_NAME + " = {" + - " support: {" + - " searchParams: 'URLSearchParams' in window," + - " iterable: 'Symbol' in window && 'iterator' in Symbol," + - " blob:" + - " 'FileReader' in window &&" + - " 'Blob' in window &&" + - " (function() {" + - " try {" + - " new Blob();" + - " return true;" + - " } catch (e) {" + - " return false;" + - " }" + - " })()," + - " formData: 'FormData' in window," + - " arrayBuffer: 'ArrayBuffer' in window" + - " }," + - " isDataView: function(obj) {" + - " return obj && DataView.prototype.isPrototypeOf(obj);" + - " }," + - " fileReaderReady: function(reader) {" + - " return new Promise(function(resolve, reject) {" + - " reader.onload = function() {" + - " resolve(reader.result);" + - " };" + - " reader.onerror = function() {" + - " reject(reader.error);" + - " };" + - " });" + - " }," + - " readBlobAsArrayBuffer: function(blob) {" + - " var reader = new FileReader();" + - " var promise = " + JAVASCRIPT_UTIL_VAR_NAME + ".fileReaderReady(reader);" + - " reader.readAsArrayBuffer(blob);" + - " return promise;" + - " }," + - " convertBodyToArrayBuffer: function(body) {" + - " var viewClasses = [" + - " '[object Int8Array]'," + - " '[object Uint8Array]'," + - " '[object Uint8ClampedArray]'," + - " '[object Int16Array]'," + - " '[object Uint16Array]'," + - " '[object Int32Array]'," + - " '[object Uint32Array]'," + - " '[object Float32Array]'," + - " '[object Float64Array]'" + - " ];" + - " var isArrayBufferView = null;" + - " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer) {" + - " isArrayBufferView =" + - " ArrayBuffer.isView ||" + - " function(obj) {" + - " return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" + - " };" + - " }" + - " var bodyUsed = false;" + - " this._bodyInit = body;" + - " if (!body) {" + - " this._bodyText = '';" + - " } else if (typeof body === 'string') {" + - " this._bodyText = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" + - " this._bodyBlob = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" + - " this._bodyFormData = body;" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" + - " this._bodyText = body.toString();" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME + ".isDataView(body)) {" + - " this._bodyArrayBuffer = bufferClone(body.buffer);" + - " this._bodyInit = new Blob([this._bodyArrayBuffer]);" + - " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" + - " this._bodyArrayBuffer = bufferClone(body);" + - " } else {" + - " this._bodyText = body = Object.prototype.toString.call(body);" + - " }" + - " this.blob = function () {" + - " if (bodyUsed) {" + - " return Promise.reject(new TypeError('Already read'));" + - " }" + - " bodyUsed = true;" + - " if (this._bodyBlob) {" + - " return Promise.resolve(this._bodyBlob);" + - " } else if (this._bodyArrayBuffer) {" + - " return Promise.resolve(new Blob([this._bodyArrayBuffer]));" + - " } else if (this._bodyFormData) {" + - " throw new Error('could not read FormData body as blob');" + - " } else {" + - " return Promise.resolve(new Blob([this._bodyText]));" + - " }" + - " };" + - " if (this._bodyArrayBuffer) {" + - " if (bodyUsed) {" + - " return Promise.reject(new TypeError('Already read'));" + - " }" + - " bodyUsed = true;" + - " if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" + - " return Promise.resolve(" + - " this._bodyArrayBuffer.buffer.slice(" + - " this._bodyArrayBuffer.byteOffset," + - " this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" + - " )" + - " );" + - " } else {" + - " return Promise.resolve(this._bodyArrayBuffer);" + - " }" + - " }" + - " return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME + ".readBlobAsArrayBuffer);" + - " }," + - " isString: function(variable) {" + - " return typeof variable === 'string' || variable instanceof String;" + - " }," + - " convertBodyRequest: function(body) {" + - " if (body == null) {" + - " return new Promise(function(resolve, reject) { resolve(null); });" + - " }" + - " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && body instanceof URLSearchParams)) {" + - " return new Promise(function(resolve, reject) { resolve(body.toString()); });" + - " }" + - " if (window.Response != null) {" + - " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + - " return Array.from(new Uint8Array(arrayBuffer));" + - " });" + - " }" + - " return " + JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" + - " return Array.from(new Uint8Array(arrayBuffer));" + - " });" + - " }," + - " arrayBufferToString: function(arrayBuffer) {" + - " var uint8Array = new Uint8Array(arrayBuffer);" + - " return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');" + - " }," + - " isBodyFormData: function(bodyString) {" + - " return bodyString.indexOf('------WebKitFormBoundary') >= 0;" + - " }," + - " getFormDataContentType: function(bodyString) {" + - " var boundary = bodyString.substr(2, 40);" + - " return 'multipart/form-data; boundary=' + boundary;" + - " }," + - " convertHeadersToJson: function(headers) {" + - " var headersObj = {};" + - " for (var header of headers.keys()) {" + - " var value = headers.get(header);" + - " headersObj[header] = value;" + - " }" + - " return headersObj;" + - " }," + - " convertJsonToHeaders: function(headersJson) {" + - " return new Headers(headersJson);" + - " }," + - " convertCredentialsToJson: function(credentials) {" + - " var credentialsObj = {};" + - " if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {" + - " credentialsObj.type = credentials.type;" + - " credentialsObj.id = credentials.id;" + - " credentialsObj.name = credentials.name;" + - " credentialsObj.protocol = credentials.protocol;" + - " credentialsObj.provider = credentials.provider;" + - " credentialsObj.iconURL = credentials.iconURL;" + - " } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {" + - " credentialsObj.type = credentials.type;" + - " credentialsObj.id = credentials.id;" + - " credentialsObj.name = credentials.name;" + - " credentialsObj.password = credentials.password;" + - " credentialsObj.iconURL = credentials.iconURL;" + - " } else {" + - " credentialsObj.type = 'default';" + - " credentialsObj.value = credentials;" + - " }" + - " return credentialsObj;" + - " }," + - " convertJsonToCredential: function(credentialsJson) {" + - " var credentials;" + - " if (window.FederatedCredential != null && credentialsJson.type === 'federated') {" + - " credentials = new FederatedCredential({" + - " id: credentialsJson.id," + - " name: credentialsJson.name," + - " protocol: credentialsJson.protocol," + - " provider: credentialsJson.provider," + - " iconURL: credentialsJson.iconURL" + - " });" + - " } else if (window.PasswordCredential != null && credentialsJson.type === 'password') {" + - " credentials = new PasswordCredential({" + - " id: credentialsJson.id," + - " name: credentialsJson.name," + - " password: credentialsJson.password," + - " iconURL: credentialsJson.iconURL" + - " });" + - " } else {" + - " credentials = credentialsJson.value == null ? undefined : credentialsJson.value;" + - " }" + - " return credentials;" + - " }" + - "};"; + public static String UTIL_JS_SOURCE() { + return JAVASCRIPT_UTIL_VAR_NAME() + " = {" + + " support: {" + + " searchParams: 'URLSearchParams' in window," + + " iterable: 'Symbol' in window && 'iterator' in Symbol," + + " blob:" + + " 'FileReader' in window &&" + + " 'Blob' in window &&" + + " (function() {" + + " try {" + + " new Blob();" + + " return true;" + + " } catch (e) {" + + " return false;" + + " }" + + " })()," + + " formData: 'FormData' in window," + + " arrayBuffer: 'ArrayBuffer' in window" + + " }," + + " isDataView: function(obj) {" + + " return obj && DataView.prototype.isPrototypeOf(obj);" + + " }," + + " fileReaderReady: function(reader) {" + + " return new Promise(function(resolve, reject) {" + + " reader.onload = function() {" + + " resolve(reader.result);" + + " };" + + " reader.onerror = function() {" + + " reject(reader.error);" + + " };" + + " });" + + " }," + + " readBlobAsArrayBuffer: function(blob) {" + + " var reader = new FileReader();" + + " var promise = " + JAVASCRIPT_UTIL_VAR_NAME() + ".fileReaderReady(reader);" + + " reader.readAsArrayBuffer(blob);" + + " return promise;" + + " }," + + " convertBodyToArrayBuffer: function(body) {" + + " var viewClasses = [" + + " '[object Int8Array]'," + + " '[object Uint8Array]'," + + " '[object Uint8ClampedArray]'," + + " '[object Int16Array]'," + + " '[object Uint16Array]'," + + " '[object Int32Array]'," + + " '[object Uint32Array]'," + + " '[object Float32Array]'," + + " '[object Float64Array]'" + + " ];" + + " var isArrayBufferView = null;" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer) {" + + " isArrayBufferView =" + + " ArrayBuffer.isView ||" + + " function(obj) {" + + " return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" + + " };" + + " }" + + " var bodyUsed = false;" + + " this._bodyInit = body;" + + " if (!body) {" + + " this._bodyText = '';" + + " } else if (typeof body === 'string') {" + + " this._bodyText = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" + + " this._bodyBlob = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" + + " this._bodyFormData = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" + + " this._bodyText = body.toString();" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME() + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME() + ".isDataView(body)) {" + + " this._bodyArrayBuffer = bufferClone(body.buffer);" + + " this._bodyInit = new Blob([this._bodyArrayBuffer]);" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" + + " this._bodyArrayBuffer = bufferClone(body);" + + " } else {" + + " this._bodyText = body = Object.prototype.toString.call(body);" + + " }" + + " this.blob = function () {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (this._bodyBlob) {" + + " return Promise.resolve(this._bodyBlob);" + + " } else if (this._bodyArrayBuffer) {" + + " return Promise.resolve(new Blob([this._bodyArrayBuffer]));" + + " } else if (this._bodyFormData) {" + + " throw new Error('could not read FormData body as blob');" + + " } else {" + + " return Promise.resolve(new Blob([this._bodyText]));" + + " }" + + " };" + + " if (this._bodyArrayBuffer) {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" + + " return Promise.resolve(" + + " this._bodyArrayBuffer.buffer.slice(" + + " this._bodyArrayBuffer.byteOffset," + + " this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" + + " )" + + " );" + + " } else {" + + " return Promise.resolve(this._bodyArrayBuffer);" + + " }" + + " }" + + " return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME() + ".readBlobAsArrayBuffer);" + + " }," + + " isString: function(variable) {" + + " return typeof variable === 'string' || variable instanceof String;" + + " }," + + " convertBodyRequest: function(body) {" + + " if (body == null) {" + + " return new Promise(function(resolve, reject) { resolve(null); });" + + " }" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME() + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME() + ".support.searchParams && body instanceof URLSearchParams)) {" + + " return new Promise(function(resolve, reject) { resolve(body.toString()); });" + + " }" + + " if (window.Response != null) {" + + " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }" + + " return " + JAVASCRIPT_UTIL_VAR_NAME() + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }," + + " arrayBufferToString: function(arrayBuffer) {" + + " var uint8Array = new Uint8Array(arrayBuffer);" + + " return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');" + + " }," + + " isBodyFormData: function(bodyString) {" + + " return bodyString.indexOf('------WebKitFormBoundary') >= 0;" + + " }," + + " getFormDataContentType: function(bodyString) {" + + " var boundary = bodyString.substr(2, 40);" + + " return 'multipart/form-data; boundary=' + boundary;" + + " }," + + " convertHeadersToJson: function(headers) {" + + " var headersObj = {};" + + " for (var header of headers.keys()) {" + + " var value = headers.get(header);" + + " headersObj[header] = value;" + + " }" + + " return headersObj;" + + " }," + + " convertJsonToHeaders: function(headersJson) {" + + " return new Headers(headersJson);" + + " }," + + " convertCredentialsToJson: function(credentials) {" + + " var credentialsObj = {};" + + " if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {" + + " credentialsObj.type = credentials.type;" + + " credentialsObj.id = credentials.id;" + + " credentialsObj.name = credentials.name;" + + " credentialsObj.protocol = credentials.protocol;" + + " credentialsObj.provider = credentials.provider;" + + " credentialsObj.iconURL = credentials.iconURL;" + + " } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {" + + " credentialsObj.type = credentials.type;" + + " credentialsObj.id = credentials.id;" + + " credentialsObj.name = credentials.name;" + + " credentialsObj.password = credentials.password;" + + " credentialsObj.iconURL = credentials.iconURL;" + + " } else {" + + " credentialsObj.type = 'default';" + + " credentialsObj.value = credentials;" + + " }" + + " return credentialsObj;" + + " }," + + " convertJsonToCredential: function(credentialsJson) {" + + " var credentials;" + + " if (window.FederatedCredential != null && credentialsJson.type === 'federated') {" + + " credentials = new FederatedCredential({" + + " id: credentialsJson.id," + + " name: credentialsJson.name," + + " protocol: credentialsJson.protocol," + + " provider: credentialsJson.provider," + + " iconURL: credentialsJson.iconURL" + + " });" + + " } else if (window.PasswordCredential != null && credentialsJson.type === 'password') {" + + " credentials = new PasswordCredential({" + + " id: credentialsJson.id," + + " name: credentialsJson.name," + + " password: credentialsJson.password," + + " iconURL: credentialsJson.iconURL" + + " });" + + " } else {" + + " credentials = credentialsJson.value == null ? undefined : credentialsJson.value;" + + " }" + + " return credentials;" + + " }" + + "};"; + } - public static final String JAVASCRIPT_BRIDGE_JS_SOURCE = "if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" + - " var _callHandlerID = setTimeout(function(){});" + - " window." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" + - " return new Promise(function(resolve, reject) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" + - " });" + - " };" + - "}"+ - "if (window.top != null && window.top !== window && window." + JAVASCRIPT_BRIDGE_NAME + " == null) {" + - " window." + JAVASCRIPT_BRIDGE_NAME + " = {};" + - " window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" + - " var _callHandlerID = setTimeout(function(){});" + - " try {" + - " window.top." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" + - " return new Promise(function(resolve, reject) {" + - " window.top." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" + - " });" + - " } catch (error) {" + - " return new Promise(function(resolve, reject) { reject(error); });" + - " }" + - " };" + - "}" + - "if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" + - " " + UTIL_JS_SOURCE + - "}"; + public static String JAVASCRIPT_BRIDGE_JS_SOURCE() { + return "if (window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null) {" + + " (function(window) {" + + " var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';" + + " var origin = '';" + + " var requestUrl = '';" + + " var isMainFrame = false;" + + " var _JSON_stringify;" + + " var _Array_slice;" + + " var _setTimeout;" + + " var _Promise;" + + " var _javaInjectedObject;" + + " try {" + + " origin = window.location.origin;" + + " } catch (_) {}" + + " try {" + + " requestUrl = window.location.href;" + + " } catch (_) {}" + + " try {" + + " isMainFrame = window === window.top;" + + " } catch (_) {}" + + " try {" + + " _JSON_stringify = window.JSON.stringify;" + + " _Array_slice = window.Array.prototype.slice;" + + " _Array_slice.call = window.Function.prototype.call;" + + " _setTimeout = window.setTimeout;" + + " _Promise = window.Promise;" + + " _javaInjectedObject = window." + get_JAVASCRIPT_BRIDGE_NAME() + ";" + + " } catch (_) { return; }" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() {" + + " var _callHandlerID = _setTimeout(function(){});" + + " _javaInjectedObject._callHandler(_JSON_stringify({" + + " 'handlerName': arguments[0]," + + " '_callHandlerID': _callHandlerID," + + " '_bridgeSecret': bridgeSecret," + + " 'origin': origin," + + " 'requestUrl': requestUrl," + + " 'isMainFrame': isMainFrame," + + " 'args': _JSON_stringify(_Array_slice.call(arguments, 1))" + + " }));" + + " return new _Promise(function(resolve, reject) {" + + " try {" + + " (isMainFrame ? window : window.top)." + get_JAVASCRIPT_BRIDGE_NAME() + "[_callHandlerID] = {resolve: resolve, reject: reject};" + + " } catch(e) { resolve(); }" + + " });" + + " };" + + " })(window);" + + "}" + + "if (window.top != null && window.top !== window && window." + get_JAVASCRIPT_BRIDGE_NAME() + " == null) {" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + " = {};" + + " (function(window) {" + + " var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';" + + " var origin = '';" + + " var requestUrl = '';" + + " var isMainFrame = false;" + + " var _JSON_stringify;" + + " var _Array_slice;" + + " var _setTimeout;" + + " var _Promise;" + + " var _javaInjectedObject;" + + " try {" + + " origin = window.location.origin;" + + " } catch (_) {}" + + " try {" + + " requestUrl = window.location.href;" + + " } catch (_) {}" + + " try {" + + " isMainFrame = window === window.top;" + + " } catch (_) {}" + + " try {" + + " _JSON_stringify = window.JSON.stringify;" + + " _Array_slice = window.Array.prototype.slice;" + + " _Array_slice.call = window.Function.prototype.call;" + + " _setTimeout = window.setTimeout;" + + " _Promise = window.Promise;" + + " _javaInjectedObject = window.top." + get_JAVASCRIPT_BRIDGE_NAME() + ";" + + " } catch (_) { return; }" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() {" + + " var _callHandlerID = _setTimeout(function(){});" + + " try {" + + " _javaInjectedObject._callHandler(_JSON_stringify({" + + " 'handlerName': arguments[0]," + + " '_callHandlerID': _callHandlerID," + + " '_bridgeSecret': bridgeSecret," + + " 'origin': origin," + + " 'requestUrl': requestUrl," + + " 'isMainFrame': isMainFrame," + + " 'args': _JSON_stringify(_Array_slice.call(arguments, 1))" + + " }));" + + " return new _Promise(function(resolve, reject) {" + + " _javaInjectedObject[_callHandlerID] = {resolve: resolve, reject: reject};" + + " });" + + " } catch (error) {" + + " return new _Promise(function(resolve, reject) { resolve(); });" + + " }" + + " };" + + " })(window);" + + "}" + + "if (window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null) {" + + " " + UTIL_JS_SOURCE() + + "}"; + } - public static final String PLATFORM_READY_JS_SOURCE = "(function() {" + - " if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + " != null && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) {" + - " window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" + - " window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady = true;" + - " }" + - "})();"; + public static String PLATFORM_READY_JS_SOURCE() { + return "(function() {" + + " if ((window.top == null || window.top === window) && window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null && window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady == null) {" + + " window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" + + " window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady = true;" + + " }" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java index b386bd7de..fdc512a50 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnLoadResourceJS.java @@ -1,35 +1,50 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnLoadResourceJS { public static final String ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"; - public static final String FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useOnLoadResource"; - public static final PluginScript ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = new PluginScript( - OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static String FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() { + return + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._useOnLoadResource"; + } + public static PluginScript ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } - public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" + - "(function() {" + - " var observer = new PerformanceObserver(function(list) {" + - " list.getEntries().forEach(function(entry) {" + - " if (" + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == null || " + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == true) {" + - " var resource = {" + - " 'url': entry.name," + - " 'initiatorType': entry.initiatorType," + - " 'startTime': entry.startTime," + - " 'duration': entry.duration" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onLoadResource', resource);" + - " }" + - " });" + - " });" + - " observer.observe({entryTypes: ['resource']});" + - "})();"; + public static String ON_LOAD_RESOURCE_JS_SOURCE() { + return + "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " = true;" + + "(function() {" + + " var observer = new PerformanceObserver(function(list) {" + + " list.getEntries().forEach(function(entry) {" + + " if (" + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " == null || " + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() + " == true) {" + + " var resource = {" + + " 'url': entry.name," + + " 'initiatorType': entry.initiatorType," + + " 'startTime': entry.startTime," + + " 'duration': entry.duration" + + " };" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onLoadResource', resource);" + + " }" + + " });" + + " });" + + " observer.observe({entryTypes: ['resource']});" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java index 746f8782a..ef2ba8c37 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowBlurEventJS.java @@ -1,22 +1,35 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnWindowBlurEventJS { public static final String ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT"; - public static final PluginScript ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = new PluginScript( - OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); - public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" + - " window.addEventListener('blur', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowBlur');" + - " });" + - "})();"; + // This plugin is only for main frame + public static PluginScript ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules) { + return + new PluginScript( + OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + true + ); + } + + public static String ON_WINDOW_BLUR_EVENT_JS_SOURCE() { + return + "(function(){" + + " window.addEventListener('blur', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWindowBlur');" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java index cfdae9a26..bf2e9a454 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/OnWindowFocusEventJS.java @@ -1,22 +1,35 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class OnWindowFocusEventJS { public static final String ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT"; - public static final PluginScript ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = new PluginScript( - OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); - public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" + - " window.addEventListener('focus', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowFocus');" + - " });" + - "})();"; + // This plugin is only for main frame + public static PluginScript ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules) { + return + new PluginScript( + OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + true + ); + } + + public static String ON_WINDOW_FOCUS_EVENT_JS_SOURCE() { + return + "(function(){" + + " window.addEventListener('focus', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWindowFocus');" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java index 91af1fdd1..de6c271d1 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PluginScriptsUtil.java @@ -1,8 +1,12 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PluginScriptsUtil { public static final String VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"; @@ -16,24 +20,30 @@ public class PluginScriptsUtil { public static final String VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID"; public static final String VAR_RANDOM_NAME = "$IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME"; - public static final String CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE = "(function(obj) {" + - " (async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") {" + - " \n" + VAR_FUNCTION_BODY + "\n" + - " })(" + VAR_FUNCTION_ARGUMENT_VALUES + ").then(function(value) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '" + VAR_RESULT_UUID + "'});" + - " }).catch(function(error) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error + '', 'resultUuid': '" + VAR_RESULT_UUID + "'});" + - " });" + - " return null;" + - "})(" + VAR_FUNCTION_ARGUMENTS_OBJ + ");"; + public static String CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE() { + return + "(function(obj) {" + + " (async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") {" + + " \n" + VAR_FUNCTION_BODY + "\n" + + " })(" + VAR_FUNCTION_ARGUMENT_VALUES + ").then(function(value) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '" + VAR_RESULT_UUID + "'});" + + " }).catch(function(error) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error + '', 'resultUuid': '" + VAR_RESULT_UUID + "'});" + + " });" + + " return null;" + + "})(" + VAR_FUNCTION_ARGUMENTS_OBJ + ");"; + } - public static final String EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE = "var $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = null;" + - "try {" + - " $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = eval(" + VAR_PLACEHOLDER_VALUE + ");" + - "} catch(e) {" + - " console.error(e);" + - "}" + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('evaluateJavaScriptWithContentWorld', {'value': $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME, 'resultUuid': '" + VAR_RESULT_UUID + "'});"; + public static String EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE() { + return + "var $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = null;" + + "try {" + + " $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = eval(" + VAR_PLACEHOLDER_VALUE + ");" + + "} catch(e) {" + + " console.error(e);" + + "}" + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('evaluateJavaScriptWithContentWorld', {'value': $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME, 'resultUuid': '" + VAR_RESULT_UUID + "'});"; + } public static final String IS_ACTIVE_ELEMENT_INPUT_EDITABLE_JS_SOURCE = "var activeEl = document.activeElement;" + @@ -71,19 +81,27 @@ public class PluginScriptsUtil { "})();"; public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME = "CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT"; - public static final PluginScript CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT = new PluginScript( - PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME, - PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static PluginScript CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME, + PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } // android Workaround to hide context menu when user emit a keydown event - public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE = "(function(){" + - " document.addEventListener('keydown', function(e) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._hideContextMenu();" + - " });" + - "})();"; + public static String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE() { + return + "(function(){" + + " document.addEventListener('keydown', function(e) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._hideContextMenu();" + + " });" + + "})();"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java index bb1084815..dc273457f 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PrintJS.java @@ -1,24 +1,36 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PrintJS { public static final String PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT"; - public static final PluginScript PRINT_JS_PLUGIN_SCRIPT = new PluginScript( - PrintJS.PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - PrintJS.PRINT_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - false, - null - ); + public static PluginScript PRINT_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return + new PluginScript( + PrintJS.PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + PrintJS.PRINT_JS_SOURCE(), + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + false, + allowedOriginRules, + forMainFrameOnly + ); + } - public static final String PRINT_JS_SOURCE = "window.print = function() {" + - " if (window.top == null || window.top === window) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onPrintRequest', window.location.href);" + - " } else {" + - " window.top.print();" + - " }" + - "};"; + public static String PRINT_JS_SOURCE() { + return + "window.print = function() {" + + " if (window.top == null || window.top === window) {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onPrintRequest', window.location.href);" + + " } else {" + + " window.top.print();" + + " }" + + "};"; + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java index af10c5d84..3331c63a0 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/plugin_scripts_js/PromisePolyfillJS.java @@ -1,18 +1,26 @@ package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js; +import androidx.annotation.Nullable; + import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime; +import java.util.Set; + public class PromisePolyfillJS { public static final String PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT"; - public static final PluginScript PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = new PluginScript( - PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, - UserScriptInjectionTime.AT_DOCUMENT_START, - null, - true, - null - ); + public static final PluginScript PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(@Nullable Set allowedOriginRules, + boolean forMainFrameOnly) { + return new PluginScript( + PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, + UserScriptInjectionTime.AT_DOCUMENT_START, + null, + true, + allowedOriginRules, + forMainFrameOnly + ); + } // https://github.com/tildeio/rsvp.js public static final String PROMISE_POLYFILL_JS_SOURCE = "if (window.Promise == null) {" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java index 1a2165670..28364aca7 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobChannelDelegate.java @@ -9,6 +9,9 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview_android.types.PrintJobInfoExt; +import java.util.HashMap; +import java.util.Map; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -62,6 +65,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result } } + public void onComplete(boolean completed, @Nullable String error) { + MethodChannel channel = getChannel(); + if (channel == null) return; + Map obj = new HashMap<>(); + obj.put("completed", completed); + obj.put("error", error); + channel.invokeMethod("onComplete", obj); + } + @Override public void dispose() { super.dispose(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java index 2283e44f6..6d962e29c 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/print_job/PrintJobController.java @@ -28,16 +28,19 @@ public class PrintJobController implements Disposable { @Nullable public PrintJobSettings settings; - public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job, - @Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) { + public PrintJobController(@NonNull String id, @Nullable PrintJobSettings settings, + @NonNull InAppWebViewFlutterPlugin plugin) { this.id = id; this.plugin = plugin; - this.job = job; this.settings = settings; final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); this.channelDelegate = new PrintJobChannelDelegate(this, channel); } - + + public void setJob(@Nullable android.print.PrintJob job) { + this.job = job; + } + public void cancel() { if (this.job != null) { this.job.cancel(); @@ -93,4 +96,8 @@ public void dispose() { } plugin = null; } + + public void onComplete(boolean completed, @Nullable String error) { + if (channelDelegate != null) channelDelegate.onComplete(completed, error); + } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java index dfbc0bfd4..3f6367329 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/pull_to_refresh/PullToRefreshLayout.java @@ -40,6 +40,8 @@ public PullToRefreshLayout(@NonNull Context context, @Nullable AttributeSet attr } public void prepare() { + setFocusable(true); + final PullToRefreshLayout self = this; setEnabled(settings.enabled); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java new file mode 100644 index 000000000..b297899ff --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/InAppWebViewRect.java @@ -0,0 +1,107 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import android.graphics.Rect; + +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class InAppWebViewRect { + private double height; + private double width; + private double x; + private double y; + + public InAppWebViewRect(double height, double width, double x, double y) { + this.height = height; + this.width = width; + this.x = x; + this.y = y; + } + + @Nullable + public static InAppWebViewRect fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + double height = (double) map.get("height"); + double width = (double) map.get("width"); + double x = (double) map.get("x"); + double y = (double) map.get("y"); + return new InAppWebViewRect(height, width, x, y); + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("height", height); + map.put("width", width); + map.put("x", x); + map.put("y", y); + return map; + } + + public Rect toRect() { + return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height)); + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InAppWebViewRect that = (InAppWebViewRect) o; + return Double.compare(height, that.height) == 0 && Double.compare(width, that.width) == 0 && Double.compare(x, that.x) == 0 && Double.compare(y, that.y) == 0; + } + + @Override + public int hashCode() { + int result = Double.hashCode(height); + result = 31 * result + Double.hashCode(width); + result = 31 * result + Double.hashCode(x); + result = 31 * result + Double.hashCode(y); + return result; + } + + @Override + public String toString() { + return "InAppWebViewRect{" + + "height=" + height + + ", width=" + width + + ", x=" + x + + ", y=" + y + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java new file mode 100644 index 000000000..8cf6aa294 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/JavaScriptHandlerFunctionData.java @@ -0,0 +1,108 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class JavaScriptHandlerFunctionData { + @NonNull + private String origin; + @NonNull + private String requestUrl; + private boolean isMainFrame; + @NonNull + private String args; + + public JavaScriptHandlerFunctionData(@NonNull String origin, @NonNull String requestUrl, boolean isMainFrame, @NonNull String args) { + this.origin = origin; + this.requestUrl = requestUrl; + this.isMainFrame = isMainFrame; + this.args = args; + } + + @Nullable + public static JavaScriptHandlerFunctionData fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + String origin = (String) map.get("origin"); + String requestUrl = (String) map.get("requestUrl"); + boolean isMainFrame = (boolean) map.get("isMainFrame"); + String args = (String) map.get("args"); + return new JavaScriptHandlerFunctionData(origin, requestUrl, isMainFrame, args); + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("origin", origin); + map.put("requestUrl", requestUrl); + map.put("isMainFrame", isMainFrame); + map.put("args", args); + return map; + } + + @NonNull + public String getRequestUrl() { + return requestUrl; + } + + public void setRequestUrl(@NonNull String requestUrl) { + this.requestUrl = requestUrl; + } + + @NonNull + public String getOrigin() { + return origin; + } + + public void setOrigin(@NonNull String origin) { + this.origin = origin; + } + + public boolean isMainFrame() { + return isMainFrame; + } + + public void setMainFrame(boolean mainFrame) { + isMainFrame = mainFrame; + } + + @NonNull + public String getArgs() { + return args; + } + + public void setArgs(@NonNull String args) { + this.args = args; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaScriptHandlerFunctionData that = (JavaScriptHandlerFunctionData) o; + return isMainFrame == that.isMainFrame && origin.equals(that.origin) && requestUrl.equals(that.requestUrl) && args.equals(that.args); + } + + @Override + public int hashCode() { + int result = origin.hashCode(); + result = 31 * result + requestUrl.hashCode(); + result = 31 * result + Boolean.hashCode(isMainFrame); + result = 31 * result + args.hashCode(); + return result; + } + + @Override + public String toString() { + return "JavaScriptHandlerFunctionData{" + + "origin='" + origin + '\'' + + ", requestUrl='" + requestUrl + '\'' + + ", isMainFrame=" + isMainFrame + + ", args='" + args + '\'' + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java index b16f22803..a5a95e4ea 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/PluginScript.java @@ -8,8 +8,9 @@ public class PluginScript extends UserScript { private boolean requiredInAllContentWorlds; - public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set allowedOriginRules) { - super(groupName, source, injectionTime, contentWorld, allowedOriginRules); + public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, + @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set allowedOriginRules, boolean forMainFrameOnly) { + super(groupName, source, injectionTime, contentWorld, allowedOriginRules, forMainFrameOnly); this.requiredInAllContentWorlds = requiredInAllContentWorlds; } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java new file mode 100644 index 000000000..eb26459b8 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java @@ -0,0 +1,140 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import android.annotation.TargetApi; +import android.os.Build; +import android.webkit.WebChromeClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserRequest { + private int mode; + @NonNull + private List acceptTypes; + private boolean isCaptureEnabled; + @Nullable + private String title; + @Nullable + private String filenameHint; + + public ShowFileChooserRequest(int mode, @NonNull List acceptTypes, boolean isCaptureEnabled, @Nullable String title, @Nullable String filenameHint) { + this.mode = mode; + this.acceptTypes = acceptTypes; + this.isCaptureEnabled = isCaptureEnabled; + this.title = title; + this.filenameHint = filenameHint; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static ShowFileChooserRequest fromFileChooserParams(WebChromeClient.FileChooserParams fileChooserParams) { + int mode = fileChooserParams.getMode(); + List acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes()); + boolean isCaptureEnabled = fileChooserParams.isCaptureEnabled(); + String title = fileChooserParams.getTitle() != null ? fileChooserParams.getTitle().toString() : null; + String filenameHint = fileChooserParams.getFilenameHint(); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + @Nullable + public static ShowFileChooserRequest fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + int mode = (int) map.get("mode"); + List acceptTypes = (List) map.get("acceptTypes"); + boolean isCaptureEnabled = (boolean) map.get("isCaptureEnabled"); + String title = (String) map.get("title"); + String filenameHint = (String) map.get("filenameHint"); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + public Map toMap() { + Map showFileChooserRequestMap = new HashMap<>(); + showFileChooserRequestMap.put("mode", mode); + showFileChooserRequestMap.put("acceptTypes", acceptTypes); + showFileChooserRequestMap.put("isCaptureEnabled", isCaptureEnabled); + showFileChooserRequestMap.put("title", title); + showFileChooserRequestMap.put("filenameHint", filenameHint); + return showFileChooserRequestMap; + } + + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public @NonNull List getAcceptTypes() { + return acceptTypes; + } + + public void setAcceptTypes(@NonNull List acceptTypes) { + this.acceptTypes = acceptTypes; + } + + public boolean isCaptureEnabled() { + return isCaptureEnabled; + } + + public void setCaptureEnabled(boolean captureEnabled) { + isCaptureEnabled = captureEnabled; + } + + @Nullable + public String getTitle() { + return title; + } + + public void setTitle(@Nullable String title) { + this.title = title; + } + + @Nullable + public String getFilenameHint() { + return filenameHint; + } + + public void setFilenameHint(@Nullable String filenameHint) { + this.filenameHint = filenameHint; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserRequest that = (ShowFileChooserRequest) o; + return mode == that.mode && isCaptureEnabled == that.isCaptureEnabled && acceptTypes.equals(that.acceptTypes) && Objects.equals(title, that.title) && Objects.equals(filenameHint, that.filenameHint); + } + + @Override + public int hashCode() { + int result = mode; + result = 31 * result + acceptTypes.hashCode(); + result = 31 * result + Boolean.hashCode(isCaptureEnabled); + result = 31 * result + Objects.hashCode(title); + result = 31 * result + Objects.hashCode(filenameHint); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserRequest{" + + "mode=" + mode + + ", acceptTypes=" + acceptTypes + + ", isCaptureEnabled=" + isCaptureEnabled + + ", title='" + title + '\'' + + ", filenameHint='" + filenameHint + '\'' + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java new file mode 100644 index 000000000..d7cc839ce --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java @@ -0,0 +1,71 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserResponse { + private boolean handledByClient; + @Nullable + private List filePaths; + + public ShowFileChooserResponse(boolean handledByClient, @Nullable List filePaths) { + this.handledByClient = handledByClient; + this.filePaths = filePaths; + } + + @Nullable + public static ShowFileChooserResponse fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + boolean handledByClient = (boolean) map.get("handledByClient"); + List filePaths = (List) map.get("filePaths"); + return new ShowFileChooserResponse(handledByClient, filePaths); + } + + public boolean isHandledByClient() { + return handledByClient; + } + + public void setHandledByClient(boolean handledByClient) { + this.handledByClient = handledByClient; + } + + @Nullable + public List getFilePaths() { + return filePaths; + } + + public void setFilePaths(@Nullable List filePaths) { + this.filePaths = filePaths; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserResponse that = (ShowFileChooserResponse) o; + return handledByClient == that.handledByClient && Objects.equals(filePaths, that.filePaths); + } + + @Override + public int hashCode() { + int result = Boolean.hashCode(handledByClient); + result = 31 * result + Objects.hashCode(filePaths); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserResponse{" + + "handledByClient=" + handledByClient + + ", filePaths=" + filePaths + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java index f51dd1b93..0e59a581d 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserContentController.java @@ -53,7 +53,7 @@ public class UserContentController implements Disposable { @Nullable public WebView webView; - public UserContentController(WebView webView) { + public UserContentController(@Nullable WebView webView) { this.webView = webView; } @@ -73,7 +73,7 @@ public String generateWrappedCodeForDocumentEnd() { } js += generatePluginScriptsCodeAt(injectionTime); js += generateUserOnlyScriptsCodeAt(injectionTime); - js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); + js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE().replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); return js; } @@ -83,7 +83,7 @@ public String generateCodeForDocumentStart() { js += generatePluginScriptsCodeAt(injectionTime); js += generateContentWorldsCreatorCode(); js += generateUserOnlyScriptsCodeAt(injectionTime); - js = USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); + js = USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE().replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); return js; } @@ -105,7 +105,7 @@ public String generateContentWorldsCreatorCode() { contentWorldsNames.add("'" + escapeContentWorldName(contentWorld.getName()) + "'"); } - return CONTENT_WORLDS_GENERATOR_JS_SOURCE + return CONTENT_WORLDS_GENERATOR_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY, TextUtils.join(", ", contentWorldsNames)) .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(source.toString())); @@ -117,6 +117,7 @@ public String generatePluginScriptsCodeAt(UserScriptInjectionTime injectionTime) for (PluginScript script : scripts) { String source = ";" + script.getSource(); source = wrapSourceCodeInContentWorld(script.getContentWorld(), source); + source = wrapSourceCodeAddChecks(source, script); js.append(source); } return js.toString(); @@ -128,6 +129,7 @@ public String generateUserOnlyScriptsCodeAt(UserScriptInjectionTime injectionTim for (UserScript script : scripts) { String source = ";" + script.getSource(); source = wrapSourceCodeInContentWorld(script.getContentWorld(), source); + source = wrapSourceCodeAddChecks(source, script); js.append(source); } return js.toString(); @@ -144,7 +146,7 @@ public String generateCodeForScriptEvaluation(String source, @Nullable ContentWo for (PluginScript script : pluginScriptsRequired) { pluginScriptsSource.append(script.getSource()); } - String contentWorldCreatorCode = CONTENT_WORLDS_GENERATOR_JS_SOURCE + String contentWorldCreatorCode = CONTENT_WORLDS_GENERATOR_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY, "'" + escapeContentWorldName(contentWorld.getName()) + "'") .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(pluginScriptsSource.toString())); sourceWrapped.append(contentWorldCreatorCode).append(";"); @@ -156,7 +158,7 @@ public String generateCodeForScriptEvaluation(String source, @Nullable ContentWo public String wrapSourceCodeInContentWorld(@Nullable ContentWorld contentWorld, String source) { String sourceWrapped = contentWorld == null || contentWorld.equals(ContentWorld.PAGE) ? source : - CONTENT_WORLD_WRAPPER_JS_SOURCE + CONTENT_WORLD_WRAPPER_JS_SOURCE() .replace(PluginScriptsUtil.VAR_CONTENT_WORLD_NAME, escapeContentWorldName(contentWorld.getName())) .replace(PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED, escapeCode(source)); @@ -201,11 +203,16 @@ public boolean addUserOnlyScript(UserScript userOnlyScript) { contentWorlds.add(contentWorld); } this.updateContentWorldsCreatorScript(); - if (webView != null && userOnlyScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_START - && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = userOnlyScript.getSource(); + if (userOnlyScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_END) { + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; + } + source = wrapSourceCodeAddChecks(source, userOnlyScript); + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( webView, - wrapSourceCodeInContentWorld(userOnlyScript.getContentWorld(), userOnlyScript.getSource()), + wrapSourceCodeInContentWorld(userOnlyScript.getContentWorld(), source), userOnlyScript.getAllowedOriginRules() ); this.scriptHandlerMap.put(userOnlyScript, scriptHandler); @@ -245,6 +252,13 @@ public void removeAllUserOnlyScripts() { this.scriptHandlerMap.remove(userOnlyScript); } } + for (UserScript userOnlyScript : this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(userOnlyScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(userOnlyScript); + } + } } this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); @@ -271,11 +285,16 @@ public boolean addPluginScript(PluginScript pluginScript) { contentWorlds.add(contentWorld); } this.updateContentWorldsCreatorScript(); - if (webView != null && pluginScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_START - && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = pluginScript.getSource(); + if (pluginScript.getInjectionTime() == UserScriptInjectionTime.AT_DOCUMENT_END) { + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; + } + source = wrapSourceCodeAddChecks(source, pluginScript); + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( webView, - wrapSourceCodeInContentWorld(pluginScript.getContentWorld(), pluginScript.getSource()), + wrapSourceCodeInContentWorld(pluginScript.getContentWorld(), source), pluginScript.getAllowedOriginRules() ); this.scriptHandlerMap.put(pluginScript, scriptHandler); @@ -310,6 +329,13 @@ public void removeAllPluginScripts() { this.scriptHandlerMap.remove(pluginScript); } } + for (PluginScript pluginScript : this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(pluginScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(pluginScript); + } + } } this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); @@ -402,60 +428,99 @@ public LinkedHashSet getContentWorlds() { return new LinkedHashSet<>(this.contentWorlds); } - private static final String USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded == null || !window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded)) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentStartLoaded = true;" + - " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + - "}"; + static private String wrapSourceCodeAddChecks(String source, UserScript userScript) { + StringBuilder ifStatement = new StringBuilder("if ("); + Set allowedOriginRules = userScript.getAllowedOriginRules(); + boolean forMainFrameOnly = userScript.isForMainFrameOnly(); + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT) && !allowedOriginRules.contains("*")) { + if (allowedOriginRules.isEmpty()) { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return ""; + } + StringBuilder jsRegExpArray = new StringBuilder("["); + for (String allowedOriginRule : allowedOriginRules) { + if (jsRegExpArray.length() > 1) { + jsRegExpArray.append(", "); + } + jsRegExpArray.append("new RegExp(").append(UserContentController.escapeCode(allowedOriginRule)).append(")"); + } + if (jsRegExpArray.length() > 1) { + jsRegExpArray.append("]"); + ifStatement.append(jsRegExpArray).append(".some(function(rx) { return rx.test(window.location.origin); })"); + } + } + if (forMainFrameOnly) { + if (ifStatement.length() > 4) { + ifStatement.append(" && "); + } + ifStatement.append("window === window.top"); + } + return ifStatement.length() > 4 ? ifStatement.append(") {").append(source).append("}").toString() : source; + } - private static final String USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded == null || !window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded)) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._userScriptsAtDocumentEndLoaded = true;" + - " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + - "}"; + private static String USER_SCRIPTS_AT_DOCUMENT_START_WRAPPER_JS_SOURCE() { + return "if (window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded == null || !window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded) {" + + " window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentStartLoaded = true;" + + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + + "}"; + } + + private static String USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE() { + return "if (window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded == null || !window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded) {" + + " window._" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_userScriptsAtDocumentEndLoaded = true;" + + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + + "}"; + } + + private static String CONTENT_WORLDS_GENERATOR_JS_SOURCE() { + return "(function() {" + + " var interval = setInterval(function() {" + + " if (document.body == null) {return;}" + + " var contentWorldNames = [" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY + "];" + + " for (var contentWorldName of contentWorldNames) {" + + " var iframeId = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_' + contentWorldName;" + + " var iframe = document.getElementById(iframeId);" + + " if (iframe == null) {" + + " iframe = document.createElement('iframe');" + + " iframe.id = iframeId;" + + " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + + " document.body.append(iframe);" + + " }" + + " if (iframe.contentWindow.document.getElementById('" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts') == null) {" + + " var script = iframe.contentWindow.document.createElement('script');" + + " script.id = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts';" + + " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + + " iframe.contentWindow.document.body.append(script);" + + " }" + + " }" + + " clearInterval(interval);" + + " });" + + "})();"; + } - private static final String CONTENT_WORLDS_GENERATOR_JS_SOURCE = "(function() {" + - " var interval = setInterval(function() {" + - " if (document.body == null) {return;}" + - " var contentWorldNames = [" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME_ARRAY + "];" + - " for (var contentWorldName of contentWorldNames) {" + - " var iframeId = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_' + contentWorldName;" + - " var iframe = document.getElementById(iframeId);" + - " if (iframe == null) {" + - " iframe = document.createElement('iframe');" + - " iframe.id = iframeId;" + - " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + - " document.body.append(iframe);" + - " }" + - " if (iframe.contentWindow.document.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts') == null) {" + - " var script = iframe.contentWindow.document.createElement('script');" + - " script.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts';" + - " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + - " iframe.contentWindow.document.body.append(script);" + - " }" + - " }" + - " clearInterval(interval);" + - " });" + - "})();"; - - private static final String CONTENT_WORLD_WRAPPER_JS_SOURCE = "(function() {" + - " var interval = setInterval(function() {" + - " if (document.body == null) {return;}" + - " var iframeId = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME + "';" + - " var iframe = document.getElementById(iframeId);" + - " if (iframe == null) {" + - " iframe = document.createElement('iframe');" + - " iframe.id = iframeId;" + - " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + - " document.body.append(iframe);" + - " }" + - " if (iframe.contentWindow.document.querySelector('#" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_plugin_scripts') == null) {" + - " return;" + - " }" + - " var script = iframe.contentWindow.document.createElement('script');" + - " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + - " iframe.contentWindow.document.body.append(script);" + - " clearInterval(interval);" + - " });" + - "})();"; + private static String CONTENT_WORLD_WRAPPER_JS_SOURCE() { + return "(function() {" + + " var interval = setInterval(function() {" + + " if (document.body == null) {return;}" + + " var iframeId = '" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_" + PluginScriptsUtil.VAR_CONTENT_WORLD_NAME + "';" + + " var iframe = document.getElementById(iframeId);" + + " if (iframe == null) {" + + " iframe = document.createElement('iframe');" + + " iframe.id = iframeId;" + + " iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" + + " document.body.append(iframe);" + + " }" + + " if (iframe.contentWindow.document.querySelector('#" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_plugin_scripts') == null) {" + + " return;" + + " }" + + " var script = iframe.contentWindow.document.createElement('script');" + + " script.innerHTML = " + PluginScriptsUtil.VAR_JSON_SOURCE_ENCODED + ";" + + " iframe.contentWindow.document.body.append(script);" + + " clearInterval(interval);" + + " });" + + "})();"; + } private static final String DOCUMENT_READY_WRAPPER_JS_SOURCE = "if (document.readyState === 'interactive' || document.readyState === 'complete') { " + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java index 1f8e5604b..d440984b2 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/UserScript.java @@ -2,10 +2,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.webkit.WebViewFeature; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; public class UserScript { @@ -19,10 +21,11 @@ public class UserScript { private ContentWorld contentWorld; @NonNull private Set allowedOriginRules = new HashSet<>(); + private boolean forMainFrameOnly = true; public UserScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, - @Nullable Set allowedOriginRules) { + @Nullable Set allowedOriginRules, boolean forMainFrameOnly) { this.groupName = groupName; this.source = source; this.injectionTime = injectionTime; @@ -30,6 +33,7 @@ public UserScript(@Nullable String groupName, @NonNull String source, this.allowedOriginRules = allowedOriginRules == null ? new HashSet() {{ add("*"); }} : allowedOriginRules; + this.forMainFrameOnly = forMainFrameOnly; } @Nullable @@ -42,8 +46,9 @@ public static UserScript fromMap(@Nullable Map map) { UserScriptInjectionTime injectionTime = UserScriptInjectionTime.fromValue((int) map.get("injectionTime")); ContentWorld contentWorld = ContentWorld.fromMap((Map) map.get("contentWorld")); Set allowedOriginRules = new HashSet<>((List) map.get("allowedOriginRules")); + boolean forMainFrameOnly = (boolean) map.get("forMainFrameOnly"); assert source != null; - return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules); + return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules, forMainFrameOnly); } @Nullable @@ -91,28 +96,31 @@ public void setAllowedOriginRules(@NonNull Set allowedOriginRules) { this.allowedOriginRules = allowedOriginRules; } + public boolean isForMainFrameOnly() { + return forMainFrameOnly; + } + + public void setForMainFrameOnly(boolean forMainFrameOnly) { + this.forMainFrameOnly = forMainFrameOnly; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserScript that = (UserScript) o; - - if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) - return false; - if (!source.equals(that.source)) return false; - if (injectionTime != that.injectionTime) return false; - if (!contentWorld.equals(that.contentWorld)) return false; - return allowedOriginRules.equals(that.allowedOriginRules); + return forMainFrameOnly == that.forMainFrameOnly && Objects.equals(groupName, that.groupName) && source.equals(that.source) && injectionTime == that.injectionTime && contentWorld.equals(that.contentWorld) && allowedOriginRules.equals(that.allowedOriginRules); } @Override public int hashCode() { - int result = groupName != null ? groupName.hashCode() : 0; + int result = Objects.hashCode(groupName); result = 31 * result + source.hashCode(); result = 31 * result + injectionTime.hashCode(); result = 31 * result + contentWorld.hashCode(); result = 31 * result + allowedOriginRules.hashCode(); + result = 31 * result + Boolean.hashCode(forMainFrameOnly); return result; } @@ -124,6 +132,7 @@ public String toString() { ", injectionTime=" + injectionTime + ", contentWorld=" + contentWorld + ", allowedOriginRules=" + allowedOriginRules + + ", forMainFrameOnly=" + forMainFrameOnly + '}'; } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java index a9b6cdf2e..be9003810 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/WebMessagePort.java @@ -36,10 +36,10 @@ public void setWebMessageCallback(final ValueCallback callback) throws Exc if (webView != null) { int index = name.equals("port1") ? 0 : 1; webView.evaluateJavascript("(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".onmessage = function (event) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWebMessagePortMessageReceived', {" + + " window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler('onWebMessagePortMessageReceived', {" + " 'webMessageChannelId': '" + webMessageChannel.id + "'," + " 'index': " + index + "," + " 'message': event.data" + @@ -82,13 +82,13 @@ public void postMessage(WebMessage message, final ValueCallback callback) throw new Exception("Port is already closed or transferred"); } port.isTransferred = true; - portArrayString.add(JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "']." + port.name); + portArrayString.add(JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "']." + port.name); } portsString = "[" + TextUtils.join(", ", portArrayString) + "]"; } String data = message.data != null ? Util.replaceAll(message.data, "\'", "\\'") : "null"; String source = "(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".postMessage('" + data + "', " + portsString + ");" + " }" + @@ -113,7 +113,7 @@ public void close(final ValueCallback callback) throws Exception { InAppWebViewInterface webView = webMessageChannel != null && webMessageChannel.webView != null ? webMessageChannel.webView : null; if (webView != null) { String source = "(function() {" + - " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME + "['" + webMessageChannel.id + "'];" + + " var webMessageChannel = " + JavaScriptBridgeJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME() + "['" + webMessageChannel.id + "'];" + " if (webMessageChannel != null) {" + " webMessageChannel." + this.name + ".close();" + " }" + diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java index ece312921..4c533c0c2 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewInterface.java @@ -52,7 +52,8 @@ void loadDataWithBaseURL(String baseUrl, String data, boolean isLoading(); void takeScreenshot(Map screenshotConfiguration, MethodChannel.Result result); void setSettings(InAppWebViewSettings newSettings, HashMap newSettingsMap); - Map getCustomSettings(); + InAppWebViewSettings getCustomSettings(); + Map getCustomSettingsMap(); HashMap getCopyBackForwardList(); void clearAllCache(); void clearSslPreferences(); @@ -116,4 +117,9 @@ void loadDataWithBaseURL(String baseUrl, String data, @Nullable WebViewChannelDelegate getChannelDelegate(); void setChannelDelegate(@Nullable WebViewChannelDelegate eventWebViewChannelDelegate); + void showInputMethod(); + void hideInputMethod(); + @Nullable + byte[] saveState(); + boolean restoreState(byte[] state); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java index 623354aef..6b909df39 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/InAppWebViewManager.java @@ -16,6 +16,7 @@ import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.FlutterWebView; @@ -32,7 +33,7 @@ public class InAppWebViewManager extends ChannelDelegateImpl { protected static final String LOG_TAG = "InAppWebViewManager"; public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"; - + @Nullable public InAppWebViewFlutterPlugin plugin; @@ -162,6 +163,23 @@ public void onReceiveValue(Boolean value) { } result.success(true); break; + case "enableSlowWholeDocumentDraw": + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + WebView.enableSlowWholeDocumentDraw(); + } + } + result.success(true); + break; + case "setJavaScriptBridgeName": + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME((String) call.argument("bridgeName")); + result.success(true); + break; + case "getJavaScriptBridgeName": + { + result.success(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); + } + break; default: result.notImplemented(); } @@ -207,9 +225,11 @@ public void dispose() { super.dispose(); Collection flutterWebViews = keepAliveWebViews.values(); for (FlutterWebView flutterWebView : flutterWebViews) { - String keepAliveId = flutterWebView.keepAliveId; - if (keepAliveId != null) { - disposeKeepAlive(flutterWebView.keepAliveId); + if (flutterWebView != null) { + String keepAliveId = flutterWebView.keepAliveId; + if (keepAliveId != null) { + disposeKeepAlive(keepAliveId); + } } } keepAliveWebViews.clear(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java index 615a1f86e..d24f89d62 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/JavaScriptBridgeInterface.java @@ -9,21 +9,27 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobController; import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobSettings; +import com.pichillilorenzo.flutter_inappwebview_android.types.JavaScriptHandlerFunctionData; import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView; -import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.regex.Pattern; + public class JavaScriptBridgeInterface { private static final String LOG_TAG = "JSBridgeInterface"; private InAppWebView inAppWebView; - - public JavaScriptBridgeInterface(InAppWebView inAppWebView) { + @NonNull + private final String expectedBridgeSecret; + + public JavaScriptBridgeInterface(InAppWebView inAppWebView, @NonNull String expectedBridgeSecret) { this.inAppWebView = inAppWebView; + this.expectedBridgeSecret = expectedBridgeSecret; } @JavascriptInterface @@ -44,11 +50,60 @@ public void run() { } @JavascriptInterface - public void _callHandler(final String handlerName, final String _callHandlerID, final String args) { + public void _callHandler(final String jsonStringifiedData) { if (inAppWebView == null) { return; } + JSONObject data; + try { + data = new JSONObject(jsonStringifiedData); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, "Cannot convert jsonStringifiedData parameter of _callHandler method to a valid JSONObject"); + return; + } + + if (!data.has("handlerName") || data.isNull("handlerName")) { + Log.d(LOG_TAG, "handlerName is null or undefined"); + return; + } + + final String handlerName = data.optString("handlerName"); + final String bridgeSecret = data.optString("_bridgeSecret"); + final Integer _callHandlerID = data.optInt("_callHandlerID"); + final String origin = data.optString("origin"); + final String requestUrl = data.optString("requestUrl"); + final Boolean isMainFrame = data.optBoolean("isMainFrame"); + final String args = data.optString("args"); + + if (!expectedBridgeSecret.equals(bridgeSecret)) { + Log.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code from origin: " + origin); + return; + } + + boolean isOriginAllowed = false; + if (inAppWebView.customSettings.javaScriptHandlersOriginAllowList != null) { + for (Pattern allowedOrigin : inAppWebView.customSettings.javaScriptHandlersOriginAllowList) { + if (allowedOrigin.matcher(origin).matches()) { + isOriginAllowed = true; + break; + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true; + } + if (!isOriginAllowed) { + Log.e(LOG_TAG, "Bridge access attempt from an origin not allowed: " + origin); + return; + } + + if (inAppWebView.customSettings.javaScriptHandlersForMainFrameOnly && !isMainFrame) { + Log.e(LOG_TAG, "Bridge access attempt from a sub-frame origin: " + origin); + return; + } + // java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. // https://github.com/pichillilorenzo/flutter_inappwebview/issues/98 final Handler handler = new Handler(inAppWebView.getWebViewLooper()); @@ -60,84 +115,99 @@ public void run() { return; } - if (handlerName.equals("onPrintRequest") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - PrintJobSettings settings = new PrintJobSettings(); - settings.handledByClient = true; - final String printJobId = inAppWebView.printCurrentPage(settings); - if (inAppWebView != null && inAppWebView.channelDelegate != null) { - inAppWebView.channelDelegate.onPrintRequest(inAppWebView.getUrl(), printJobId, new WebViewChannelDelegate.PrintRequestCallback() { - @Override - public boolean nonNullSuccess(@NonNull Boolean handledByClient) { - return !handledByClient; - } + boolean isInternalHandler = true; + switch (handlerName) { + case "onPrintRequest": + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + PrintJobSettings settings = new PrintJobSettings(); + settings.handledByClient = true; + final String printJobId = inAppWebView.printCurrentPage(settings); + if (inAppWebView != null && inAppWebView.channelDelegate != null) { + inAppWebView.channelDelegate.onPrintRequest(inAppWebView.getUrl(), printJobId, new WebViewChannelDelegate.PrintRequestCallback() { + @Override + public boolean nonNullSuccess(@NonNull Boolean handledByClient) { + return !handledByClient; + } - @Override - public void defaultBehaviour(@Nullable Boolean handledByClient) { - if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) { - PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId); - if (printJobController != null) { - printJobController.disposeNoCancel(); + @Override + public void defaultBehaviour(@Nullable Boolean handledByClient) { + if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) { + PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId); + if (printJobController != null) { + printJobController.disposeNoCancel(); + } + } } - } - } - @Override - public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { - Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); - defaultBehaviour(null); + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }); } - }); - } - return; - } else if (handlerName.equals("callAsyncJavaScript")) { - try { - JSONArray arguments = new JSONArray(args); - JSONObject jsonObject = arguments.getJSONObject(0); - String resultUuid = jsonObject.getString("resultUuid"); - ValueCallback callAsyncJavaScriptCallback = inAppWebView.callAsyncJavaScriptCallbacks.get(resultUuid); - if (callAsyncJavaScriptCallback != null) { - callAsyncJavaScriptCallback.onReceiveValue(jsonObject.toString()); - inAppWebView.callAsyncJavaScriptCallbacks.remove(resultUuid); } - } catch (JSONException e) { - Log.e(LOG_TAG, "", e); - } - return; - } else if (handlerName.equals("evaluateJavaScriptWithContentWorld")) { - try { - JSONArray arguments = new JSONArray(args); - JSONObject jsonObject = arguments.getJSONObject(0); - String resultUuid = jsonObject.getString("resultUuid"); - ValueCallback evaluateJavaScriptCallback = inAppWebView.evaluateJavaScriptContentWorldCallbacks.get(resultUuid); - if (evaluateJavaScriptCallback != null) { - evaluateJavaScriptCallback.onReceiveValue(jsonObject.has("value") ? jsonObject.get("value").toString() : "null"); - inAppWebView.evaluateJavaScriptContentWorldCallbacks.remove(resultUuid); + break; + case "callAsyncJavaScript": + try { + JSONArray arguments = new JSONArray(args); + JSONObject jsonObject = arguments.getJSONObject(0); + String resultUuid = jsonObject.getString("resultUuid"); + ValueCallback callAsyncJavaScriptCallback = inAppWebView.callAsyncJavaScriptCallbacks.get(resultUuid); + if (callAsyncJavaScriptCallback != null) { + callAsyncJavaScriptCallback.onReceiveValue(jsonObject.toString()); + inAppWebView.callAsyncJavaScriptCallbacks.remove(resultUuid); + } + } catch (JSONException e) { + Log.e(LOG_TAG, "", e); } - } catch (JSONException e) { - Log.e(LOG_TAG, "", e); + break; + case "evaluateJavaScriptWithContentWorld": + try { + JSONArray arguments = new JSONArray(args); + JSONObject jsonObject = arguments.getJSONObject(0); + String resultUuid = jsonObject.getString("resultUuid"); + ValueCallback evaluateJavaScriptCallback = inAppWebView.evaluateJavaScriptContentWorldCallbacks.get(resultUuid); + if (evaluateJavaScriptCallback != null) { + evaluateJavaScriptCallback.onReceiveValue(jsonObject.has("value") ? jsonObject.get("value").toString() : "null"); + inAppWebView.evaluateJavaScriptContentWorldCallbacks.remove(resultUuid); + } + } catch (JSONException e) { + Log.e(LOG_TAG, "", e); + } + break; + default: + isInternalHandler = false; + break; + } + + if (isInternalHandler) { + if (inAppWebView != null) { + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].resolve(); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + + "}"; + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } return; } + if (inAppWebView.channelDelegate != null) { + JavaScriptHandlerFunctionData data = new JavaScriptHandlerFunctionData(origin, requestUrl, isMainFrame, args); // invoke flutter javascript handler and send back flutter data as a JSON Object to javascript - inAppWebView.channelDelegate.onCallJsHandler(handlerName, args, new WebViewChannelDelegate.CallJsHandlerCallback() { + inAppWebView.channelDelegate.onCallJsHandler(handlerName, data, new WebViewChannelDelegate.CallJsHandlerCallback() { @Override public void defaultBehaviour(@Nullable Object json) { if (inAppWebView == null) { // The webview has already been disposed, ignore. return; } - String sourceCode = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "] != null) { " + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "].resolve(" + json + "); " + - "delete window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "]; " + - "}"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); - } - else { - inAppWebView.loadUrl("javascript:" + sourceCode); - } + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].resolve(" + json + "); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + + "}"; + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } @Override @@ -150,16 +220,11 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj return; } - String sourceCode = "if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "] != null) { " + - "window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "].reject(new Error(" + JSONObject.quote(message) + ")); " + - "delete window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "[" + _callHandlerID + "]; " + + String sourceCode = "if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "] != null) { " + + "window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "].reject(new Error(" + JSONObject.quote(message) + ")); " + + "delete window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "[" + _callHandlerID + "]; " + "}"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); - } - else { - inAppWebView.loadUrl("javascript:" + sourceCode); - } + inAppWebView.evaluateJavascript(sourceCode, (ValueCallback) null); } }); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java index 5f87b93c0..8bcf63ce6 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java @@ -29,6 +29,8 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.HitTestResult; import com.pichillilorenzo.flutter_inappwebview_android.types.HttpAuthResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.HttpAuthenticationChallenge; +import com.pichillilorenzo.flutter_inappwebview_android.types.InAppWebViewRect; +import com.pichillilorenzo.flutter_inappwebview_android.types.JavaScriptHandlerFunctionData; import com.pichillilorenzo.flutter_inappwebview_android.types.JsAlertResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsBeforeUnloadResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsConfirmResponse; @@ -39,6 +41,8 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.SafeBrowsingResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustAuthResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustChallenge; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.SslCertificateExt; import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; @@ -231,9 +235,9 @@ public void onReceiveValue(String value) { case getSettings: if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) { InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate(); - result.success(inAppBrowserActivity.getCustomSettings()); + result.success(inAppBrowserActivity.getCustomSettingsMap()); } else { - result.success((webView != null) ? webView.getCustomSettings() : null); + result.success((webView != null) ? webView.getCustomSettingsMap() : null); } break; case close: @@ -474,6 +478,23 @@ public void onReceiveValue(String value) { } result.success(true); break; + case requestFocus: + if (webView != null) { + boolean resultValue = false; + Integer direction = (Integer) call.argument("direction"); + InAppWebViewRect previouslyFocusedRect = InAppWebViewRect.fromMap((Map) call.argument("previouslyFocusedRect")); + if (direction != null && previouslyFocusedRect != null) { + resultValue = webView.requestFocus(direction, previouslyFocusedRect.toRect()); + } else if (direction != null) { + resultValue = webView.requestFocus(direction); + } else { + resultValue = webView.requestFocus(); + } + result.success(resultValue); + } else { + result.success(false); + } + break; case setContextMenu: if (webView != null) { Map contextMenu = (Map) call.argument("contextMenu"); @@ -675,6 +696,37 @@ public void onReceiveValue(Boolean value) { webView.clearFormData(); } result.success(true); + case hideInputMethod: + if (webView != null) { + webView.hideInputMethod(); + result.success(true); + } else { + result.success(false); + } + break; + case showInputMethod: + if (webView != null) { + webView.showInputMethod(); + result.success(true); + } else { + result.success(false); + } + break; + case saveState: + if (webView != null) { + result.success(webView.saveState()); + } else { + result.success(null); + } + break; + case restoreState: + if (webView != null) { + byte[] state = (byte[]) call.argument("state"); + result.success(webView.restoreState(state)); + } else { + result.success(false); + } + break; } } @@ -707,10 +759,10 @@ public void onScrollChanged(int x, int y) { channel.invokeMethod("onScrollChanged", obj); } - public void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) { + public void onDownloadStarting(DownloadStartRequest downloadStartRequest) { MethodChannel channel = getChannel(); if (channel == null) return; - channel.invokeMethod("onDownloadStartRequest", downloadStartRequest.toMap()); + channel.invokeMethod("onDownloadStarting", downloadStartRequest.toMap()); } public void onCreateContextMenu(HitTestResult hitTestResult) { @@ -1271,7 +1323,7 @@ public Object decodeResult(@Nullable Object obj) { } } - public void onCallJsHandler(String handlerName, String args, @NonNull CallJsHandlerCallback callback) { + public void onCallJsHandler(String handlerName, JavaScriptHandlerFunctionData data, @NonNull CallJsHandlerCallback callback) { MethodChannel channel = getChannel(); if (channel == null) { callback.defaultBehaviour(null); @@ -1279,7 +1331,7 @@ public void onCallJsHandler(String handlerName, String args, @NonNull CallJsHand } Map obj = new HashMap<>(); obj.put("handlerName", handlerName); - obj.put("args", args); + obj.put("data", data.toMap()); channel.invokeMethod("onCallJsHandler", obj, callback); } @@ -1310,6 +1362,23 @@ public void onRequestFocus() { channel.invokeMethod("onRequestFocus", obj); } + public static class ShowFileChooserCallback extends BaseCallbackResultImpl { + @Nullable + @Override + public ShowFileChooserResponse decodeResult(@Nullable Object obj) { + return ShowFileChooserResponse.fromMap((Map) obj); + } + } + + public void onShowFileChooser(ShowFileChooserRequest request, @NonNull ShowFileChooserCallback callback) { + MethodChannel channel = getChannel(); + if (channel == null) { + callback.defaultBehaviour(null); + return; + } + channel.invokeMethod("onShowFileChooser", request.toMap(), callback); + } + @Override public void dispose() { super.dispose(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java index 3c6d6ae1f..45f022ef6 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegateMethods.java @@ -62,6 +62,7 @@ public enum WebViewChannelDelegateMethods { saveWebArchive, zoomIn, zoomOut, + requestFocus, clearFocus, setContextMenu, requestFocusNodeHref, @@ -82,5 +83,9 @@ public enum WebViewChannelDelegateMethods { canScrollVertically, canScrollHorizontally, isInFullscreen, - clearFormData + clearFormData, + hideInputMethod, + showInputMethod, + saveState, + restoreState, } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java index b0bfd5509..27a658979 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebView.java @@ -18,10 +18,14 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.print.InAppWebViewPrintDocumentAdapter; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; +import android.print.PrintJob; import android.print.PrintManager; import android.text.TextUtils; import android.util.AttributeSet; @@ -107,13 +111,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; -import java.util.regex.Pattern; import io.flutter.plugin.common.MethodChannel; final public class InAppWebView extends InputAwareWebView implements InAppWebViewInterface { - protected static final String LOG_TAG = "InAppWebView"; + private static final String LOG_TAG = "InAppWebView"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"; @Nullable @@ -140,7 +144,6 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie private boolean inFullscreen = false; public float zoomScale = 1.0f; public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); - public Pattern regexToCancelSubFramesLoadingCompiled; @Nullable public GestureDetector gestureDetector = null; @Nullable @@ -176,6 +179,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie @Nullable private PluginScript interceptOnlyAsyncAjaxRequestsPluginScript; + @NonNull + private final String expectedBridgeSecret = UUID.randomUUID().toString(); + private boolean javaScriptBridgeEnabled = true; + public InAppWebView(Context context) { super(context); } @@ -215,8 +222,8 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega } boolean isChromiumWebView = "com.android.webview".equals(packageInfo.packageName) || - "com.google.android.webview".equals(packageInfo.packageName) || - "com.android.chrome".equals(packageInfo.packageName); + "com.google.android.webview".equals(packageInfo.packageName) || + "com.android.chrome".equals(packageInfo.packageName); boolean isChromiumWebViewBugFixed = false; if (isChromiumWebView) { String versionName = packageInfo.versionName != null ? packageInfo.versionName : ""; @@ -224,7 +231,8 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega int majorVersion = versionName.contains(".") ? Integer.parseInt(versionName.split("\\.")[0]) : 0; isChromiumWebViewBugFixed = majorVersion >= 73; - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + } } if (isChromiumWebViewBugFixed || !isChromiumWebView) { @@ -236,14 +244,36 @@ public WebViewClient createWebViewClient(InAppBrowserDelegate inAppBrowserDelega } } + @Override + public void setAlpha(float alpha) { + ViewParent parent = getParent(); + if (parent instanceof PullToRefreshLayout) { + ((PullToRefreshLayout) parent).setAlpha(alpha); + } else { + super.setAlpha(alpha); + } + } + @SuppressLint("RestrictedApi") public void prepare() { + if (customSettings.alpha != null) { + setAlpha(customSettings.alpha.floatValue()); + } + + javaScriptBridgeEnabled = customSettings.javaScriptBridgeEnabled; + if (customSettings.javaScriptBridgeOriginAllowList != null && customSettings.javaScriptBridgeOriginAllowList.isEmpty()) { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false; + } + if (plugin != null) { webViewAssetLoaderExt = WebViewAssetLoaderExt.fromMap(customSettings.webViewAssetLoader, plugin, getContext()); } - javaScriptBridgeInterface = new JavaScriptBridgeInterface(this); - addJavascriptInterface(javaScriptBridgeInterface, JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME); + if (javaScriptBridgeEnabled) { + javaScriptBridgeInterface = new JavaScriptBridgeInterface(this, expectedBridgeSecret); + addJavascriptInterface(javaScriptBridgeInterface, JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); + } inAppWebViewChromeClient = new InAppWebViewChromeClient(plugin, this, inAppBrowserDelegate); setWebChromeClient(inAppWebViewChromeClient); @@ -315,7 +345,8 @@ else if (customSettings.clearSessionCache) settings.setLoadWithOverviewMode(customSettings.loadWithOverviewMode); settings.setUseWideViewPort(customSettings.useWideViewPort); settings.setSupportZoom(customSettings.supportZoom); - settings.setTextZoom(customSettings.textZoom); + if (customSettings.textZoom != null) + settings.setTextZoom(customSettings.textZoom); setVerticalScrollBarEnabled(!customSettings.disableVerticalScroll && customSettings.verticalScrollBarEnabled); setHorizontalScrollBarEnabled(!customSettings.disableHorizontalScroll && customSettings.horizontalScrollBarEnabled); @@ -359,7 +390,13 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) settings.setForceDark(customSettings.forceDark); } if (customSettings.forceDarkStrategy != null && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { - WebSettingsCompat.setForceDarkStrategy(settings, customSettings.forceDarkStrategy); + try { + // for some reason the setForceDarkStrategy method could throw a ClassCastException + // from the Android WebView Chromium library. + WebSettingsCompat.setForceDarkStrategy(settings, customSettings.forceDarkStrategy); + } catch (Exception e) { + e.printStackTrace(); + } } settings.setGeolocationEnabled(customSettings.geolocationEnabled); if (customSettings.layoutAlgorithm != null) { @@ -394,9 +431,6 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) else setLayerType(View.LAYER_TYPE_NONE, null); } - if (customSettings.regexToCancelSubFramesLoading != null) { - regexToCancelSubFramesLoadingCompiled = Pattern.compile(customSettings.regexToCancelSubFramesLoading); - } setScrollBarStyle(customSettings.scrollBarStyle); if (customSettings.scrollBarDefaultDelayBeforeFade != null) { setScrollBarDefaultDelayBeforeFade(customSettings.scrollBarDefaultDelayBeforeFade); @@ -560,24 +594,44 @@ public boolean onLongClick(View v) { } public void prepareAndAddUserScripts() { - userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT); - interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(customSettings.interceptOnlyAsyncAjaxRequests); - if (customSettings.useShouldInterceptAjaxRequest) { - userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript); - userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useShouldInterceptFetchRequest) { - userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useOnLoadResource) { - userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT); - } - if (!customSettings.useHybridComposition) { - userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT); + if (javaScriptBridgeEnabled) { + // all the plugin scripts are using the JavaScript Bridge to work + userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + + final Set javaScriptBridgeOriginAllowList = customSettings.javaScriptBridgeOriginAllowList != null ? + customSettings.javaScriptBridgeOriginAllowList : customSettings.pluginScriptsOriginAllowList; + final boolean javaScriptBridgeForMainFrameOnly = customSettings.javaScriptBridgeForMainFrameOnly != null ? + customSettings.javaScriptBridgeForMainFrameOnly : customSettings.pluginScriptsForMainFrameOnly; + userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret, + javaScriptBridgeOriginAllowList, + javaScriptBridgeForMainFrameOnly)); + + userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList)); + userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList)); + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(customSettings.interceptOnlyAsyncAjaxRequests); + if (customSettings.useShouldInterceptAjaxRequest) { + userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript); + userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT( + customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly, + customSettings.useOnAjaxReadyStateChange, + customSettings.useOnAjaxProgress)); + } + if (customSettings.useShouldInterceptFetchRequest) { + userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } + if (customSettings.useOnLoadResource) { + userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } + if (!customSettings.useHybridComposition) { + userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT(customSettings.pluginScriptsOriginAllowList, + customSettings.pluginScriptsForMainFrameOnly)); + } } this.userContentController.addUserOnlyScripts(this.initialUserOnlyScripts); } @@ -704,36 +758,21 @@ public void takeScreenshot(final @Nullable Map screenshotConfigu @Override public void run() { try { - Bitmap screenshotBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(screenshotBitmap); - c.translate(-getScrollX(), -getScrollY()); - draw(c); + int bitmapWidth = getMeasuredWidth(); + int bitmapHeight = getMeasuredHeight(); + int bitmapScrollX = getScrollX(); + int bitmapScrollY = getScrollY(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.PNG; int quality = 100; if (screenshotConfiguration != null) { Map rect = (Map) screenshotConfiguration.get("rect"); if (rect != null) { - int rectX = (int) Math.floor(rect.get("x") * pixelDensity + 0.5); - int rectY = (int) Math.floor(rect.get("y") * pixelDensity + 0.5); - int rectWidth = Math.min(screenshotBitmap.getWidth(), (int) Math.floor(rect.get("width") * pixelDensity + 0.5)); - int rectHeight = Math.min(screenshotBitmap.getHeight(), (int) Math.floor(rect.get("height") * pixelDensity + 0.5)); - screenshotBitmap = Bitmap.createBitmap( - screenshotBitmap, - rectX, - rectY, - rectWidth, - rectHeight); - } - - Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); - if (snapshotWidth != null) { - int dstWidth = (int) Math.floor(snapshotWidth * pixelDensity + 0.5); - float ratioBitmap = (float) screenshotBitmap.getWidth() / (float) screenshotBitmap.getHeight(); - int dstHeight = (int) ((float) dstWidth / ratioBitmap); - screenshotBitmap = Bitmap.createScaledBitmap(screenshotBitmap, dstWidth, dstHeight, true); + bitmapScrollX = (int) Math.floor(rect.get("x") * pixelDensity + 0.5); + bitmapScrollY = (int) Math.floor(rect.get("y") * pixelDensity + 0.5); + bitmapWidth = (int) Math.floor(rect.get("width") * pixelDensity + 0.5); + bitmapHeight = (int) Math.floor(rect.get("height") * pixelDensity + 0.5); } try { @@ -745,10 +784,31 @@ public void run() { quality = (Integer) screenshotConfiguration.get("quality"); } - screenshotBitmap.compress( + Bitmap screenshotBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(screenshotBitmap); + c.translate(-bitmapScrollX, -bitmapScrollY); + draw(c); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + if (screenshotConfiguration != null) { + Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); + if (snapshotWidth != null) { + int dstWidth = (int) Math.floor(snapshotWidth * pixelDensity + 0.5); + float ratioBitmap = (float) screenshotBitmap.getWidth() / (float) screenshotBitmap.getHeight(); + int dstHeight = (int) ((float) dstWidth / ratioBitmap); + screenshotBitmap = Bitmap.createScaledBitmap(screenshotBitmap, dstWidth, dstHeight, true); + } + } + + final boolean compressed = screenshotBitmap.compress( compressFormat, quality, byteArrayOutputStream); + if (!compressed) { + Log.e(LOG_TAG, "Screenshot cannot be compressed using compressFormat " + + compressFormat.name() + " with quality " + quality, null); + } try { byteArrayOutputStream.close(); @@ -776,15 +836,27 @@ public void setSettings(InAppWebViewSettings newCustomSettings, HashMap= Build.VERSION_CODES.N) @@ -955,7 +1029,13 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (newSettingsMap.get("forceDarkStrategy") != null && !customSettings.forceDarkStrategy.equals(newCustomSettings.forceDarkStrategy) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { - WebSettingsCompat.setForceDarkStrategy(settings, newCustomSettings.forceDarkStrategy); + try { + // for some reason the setForceDarkStrategy method could throw a ClassCastException + // from the Android WebView Chromium library. + WebSettingsCompat.setForceDarkStrategy(settings, newCustomSettings.forceDarkStrategy); + } catch (Exception e) { + e.printStackTrace(); + } } if (newSettingsMap.get("geolocationEnabled") != null && customSettings.geolocationEnabled != newCustomSettings.geolocationEnabled) @@ -1030,14 +1110,6 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) } } - if (newSettingsMap.get("regexToCancelSubFramesLoading") != null && (customSettings.regexToCancelSubFramesLoading == null || - !customSettings.regexToCancelSubFramesLoading.equals(newCustomSettings.regexToCancelSubFramesLoading))) { - if (newCustomSettings.regexToCancelSubFramesLoading == null) - regexToCancelSubFramesLoadingCompiled = null; - else - regexToCancelSubFramesLoadingCompiled = Pattern.compile(customSettings.regexToCancelSubFramesLoading); - } - if (newCustomSettings.contentBlockers != null) { contentBlockerHandler.getRuleList().clear(); for (Map> contentBlocker : newCustomSettings.contentBlockers) { @@ -1126,24 +1198,24 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) customSettings = newCustomSettings; } - public Map getCustomSettings() { + public Map getCustomSettingsMap() { return (customSettings != null) ? customSettings.getRealSettings(this) : null; } public void enablePluginScriptAtRuntime(final String flagVariable, final boolean enable, final PluginScript pluginScript) { - evaluateJavascript("window." + flagVariable, null, new ValueCallback() { + evaluateJavascript("((window.top == null || window.top === window) ? window : window.top)." + flagVariable, null, new ValueCallback() { @Override public void onReceiveValue(String value) { boolean alreadyLoaded = value != null && !value.equalsIgnoreCase("null"); if (alreadyLoaded) { - String enableSource = "window." + flagVariable + " = " + enable + ";"; + String enableSource = "((window.top == null || window.top === window) ? window : window.top)." + flagVariable + " = " + enable + ";"; evaluateJavascript(enableSource, null, null); if (!enable) { userContentController.removePluginScript(pluginScript); } - } else if (enable) { + } else if (enable && javaScriptBridgeEnabled) { evaluateJavascript(pluginScript.getSource(), null, null); userContentController.addPluginScript(pluginScript); } @@ -1163,8 +1235,8 @@ public void injectDeferredObject(String source, @Nullable final ContentWorld con } if (resultUuid != null && resultCallback != null) { evaluateJavaScriptContentWorldCallbacks.put(resultUuid, resultCallback); - scriptToInject = Util.replaceAll(PluginScriptsUtil.EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE, - PluginScriptsUtil.VAR_RANDOM_NAME, "_" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "_" + Math.round(Math.random() * 1000000)) + scriptToInject = Util.replaceAll(PluginScriptsUtil.EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE(), + PluginScriptsUtil.VAR_RANDOM_NAME, "_" + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "_" + Math.round(Math.random() * 1000000)) .replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, UserContentController.escapeCode(source)) .replace(PluginScriptsUtil.VAR_RESULT_UUID, resultUuid); } @@ -1173,22 +1245,14 @@ public void injectDeferredObject(String source, @Nullable final ContentWorld con @Override public void run() { String scriptToInject = userContentController.generateCodeForScriptEvaluation(finalScriptToInject, contentWorld); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - // This action will have the side-effect of blurring the currently focused element - loadUrl("javascript:" + scriptToInject.replaceAll("[\r\n]+", "")); - if (contentWorld != null && resultCallback != null) { - resultCallback.onReceiveValue(""); + evaluateJavascript(scriptToInject, new ValueCallback() { + @Override + public void onReceiveValue(String s) { + if (resultUuid != null || resultCallback == null) + return; + resultCallback.onReceiveValue(s); } - } else { - evaluateJavascript(scriptToInject, new ValueCallback() { - @Override - public void onReceiveValue(String s) { - if (resultUuid != null || resultCallback == null) - return; - resultCallback.onReceiveValue(s); - } - }); - } + }); } }); } @@ -1209,15 +1273,15 @@ public void injectJavascriptFileFromUrl(String urlFile, @Nullable Map argumen String functionArgumentValues = TextUtils.join(", ", functionArgumentValuesList); String functionArgumentsObj = Util.JSONStringify(arguments); - String sourceToInject = PluginScriptsUtil.CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE + String sourceToInject = PluginScriptsUtil.CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE() .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, functionArgumentNames) .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, functionArgumentValues) .replace(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, functionArgumentsObj) @@ -2026,6 +2108,69 @@ public void setChannelDelegate(@Nullable WebViewChannelDelegate channelDelegate) this.channelDelegate = channelDelegate; } + @Override + public InAppWebViewSettings getCustomSettings() { + return customSettings; + } + + @Override + public void showInputMethod() { + if (plugin == null || plugin.activity == null) { + return; + } + InputMethodManager imm = (InputMethodManager) plugin.activity.getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(this, 0); + } + } + + @Override + public void hideInputMethod() { + if (plugin == null || plugin.activity == null) { + return; + } + InputMethodManager imm = (InputMethodManager) plugin.activity.getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + IBinder windowToken = getWindowToken(); + if (!customSettings.useHybridComposition && containerView != null) { + windowToken = containerView.getWindowToken(); + } + imm.hideSoftInputFromWindow(windowToken, 0); + } + } + + @Override + @Nullable + public byte[] saveState() { + Bundle bundle = new Bundle(); + if (saveState(bundle) != null) { + Parcel parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } + return null; + } + + @Override + public boolean restoreState(byte[] state) { + boolean restored = false; + Parcel parcel = Parcel.obtain(); + try { + parcel.unmarshall(state, 0, state.length); + parcel.setDataPosition(0); + Bundle bundle = Bundle.CREATOR.createFromParcel(parcel); + restored = restoreState(bundle) != null; + } catch (Exception e) { + e.printStackTrace(); + } finally { + parcel.recycle(); + } + return restored; + } + + @Override public void dispose() { if (channelDelegate != null) { @@ -2035,7 +2180,7 @@ public void dispose() { super.dispose(); WebSettings settings = getSettings(); settings.setJavaScriptEnabled(false); - removeJavascriptInterface(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME); + removeJavascriptInterface(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE)) { WebViewCompat.setWebViewRenderProcessClient(this, null); } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java index 2f328f345..120962808 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java @@ -1,5 +1,7 @@ package com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview; +import static android.app.Activity.RESULT_OK; + import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; @@ -43,16 +45,18 @@ import androidx.core.content.FileProvider; import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider; -import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; +import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.ActivityResultListener; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserDelegate; -import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; import com.pichillilorenzo.flutter_inappwebview_android.types.GeolocationPermissionShowPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsAlertResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsBeforeUnloadResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsConfirmResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.PermissionResponse; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview_android.webview.WebViewChannelDelegate; @@ -62,12 +66,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.PluginRegistry; -import static android.app.Activity.RESULT_OK; - public class InAppWebViewChromeClient extends WebChromeClient implements PluginRegistry.ActivityResultListener, ActivityResultListener { protected static final String LOG_TAG = "IABWebChromeClient"; @@ -76,7 +79,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR private static final int PICKER = 1; private static final int PICKER_LEGACY = 3; final String DEFAULT_MIME_TYPES = "*/*"; - final Map dialogs = new HashMap(); + final Map dialogs = new HashMap<>(); protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); @@ -524,7 +527,7 @@ public void onCancel(DialogInterface dialog) { @Override public boolean onJsBeforeUnload(final WebView view, String url, final String message, - final JsResult result) { + final JsResult result) { if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onJsBeforeUnload(url, message, new WebViewChannelDelegate.JsBeforeUnloadCallback() { @Override @@ -634,13 +637,13 @@ public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGest String url = result.getExtra(); // Ensure that images with hyperlink return the correct URL, not the image source - if(result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { + if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { Message href = view.getHandler().obtainMessage(); view.requestFocusNodeHref(href); Bundle data = href.getData(); if (data != null) { String imageUrl = data.getString("url"); - if(imageUrl != null && !imageUrl.isEmpty()) { + if (imageUrl != null && !imageUrl.isEmpty()) { url = imageUrl; } } @@ -800,8 +803,8 @@ public void onReceivedIcon(WebView view, Bitmap icon) { @Override public void onReceivedTouchIconUrl(WebView view, - String url, - boolean precomposed) { + String url, + boolean precomposed) { super.onReceivedTouchIconUrl(view, url, precomposed); InAppWebView webView = (InAppWebView) view; @@ -819,25 +822,78 @@ protected ViewGroup getRootView() { return (ViewGroup) activity.findViewById(android.R.id.content); } + private boolean onShowFileChooser(@NonNull ShowFileChooserRequest request, @NonNull ValueCallback filePathsCallback) { + WebViewChannelDelegate.ShowFileChooserCallback callback = new WebViewChannelDelegate.ShowFileChooserCallback() { + @Override + public boolean nonNullSuccess(@NonNull ShowFileChooserResponse response) { + if (response.isHandledByClient()) { + Uri[] uriArray = null; + if (response.getFilePaths() != null) { + uriArray = new Uri[response.getFilePaths().size()]; + for (int i = 0; i < response.getFilePaths().size(); i++) { + uriArray[i] = Uri.parse(response.getFilePaths().get(i)); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray); + } else { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray != null ? uriArray[0] : null); + } + return false; + } + return true; + } + + @Override + public void defaultBehaviour(@Nullable ShowFileChooserResponse response) { + String[] acceptTypes = request.getAcceptTypes().toArray(new String[0]); + boolean captureEnabled = request.isCaptureEnabled(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + boolean allowMultiple = request.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes, allowMultiple, captureEnabled); + } else { + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes.length > 0 ? acceptTypes[0] : "", captureEnabled); + } + } + + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }; + + if (inAppWebView != null && inAppWebView.channelDelegate != null && inAppWebView.customSettings.useOnShowFileChooser) { + inAppWebView.channelDelegate.onShowFileChooser(request, callback); + } else { + callback.defaultBehaviour(null); + } + + return true; + } + protected void openFileChooser(ValueCallback filePathCallback, String acceptType) { - startPickerIntent(filePathCallback, acceptType, null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback) { - startPickerIntent(filePathCallback, "", null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(""); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { - startPickerIntent(filePathCallback, acceptType, capture); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, true, null, null), filePathCallback); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - String[] acceptTypes = fileChooserParams.getAcceptTypes(); - boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; - boolean captureEnabled = fileChooserParams.isCaptureEnabled(); - return startPickerIntent(filePathCallback, acceptTypes, allowMultiple, captureEnabled); + return onShowFileChooser(ShowFileChooserRequest.fromFileChooserParams(fileChooserParams), filePathCallback); } @Override @@ -939,7 +995,7 @@ private Uri getCapturedMediaFile() { return null; } - public void startPickerIntent(ValueCallback filePathCallback, String acceptType, @Nullable String capture) { + public void startPickerIntent(ValueCallback filePathCallback, String acceptType, boolean captureEnabled) { filePathCallbackLegacy = filePathCallback; boolean images = acceptsImages(acceptType); @@ -947,12 +1003,11 @@ public void startPickerIntent(ValueCallback filePathCallback, String accept Intent pickerIntent = null; - if (capture != null) { + if (captureEnabled) { if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -995,8 +1050,7 @@ public boolean startPickerIntent(final ValueCallback callback, final Stri if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -1271,8 +1325,8 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequest(request.getOrigin().toString(), Arrays.asList(request.getResources()), null, callback); } else { @@ -1283,17 +1337,17 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj @Override public void onRequestFocus(WebView view) { - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onRequestFocus(); } } @Override public void onPermissionRequestCanceled(PermissionRequest request) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequestCanceled(request.getOrigin().toString(), - Arrays.asList(request.getResources())); + Arrays.asList(request.getResources())); } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java index c964c0327..aa5949526 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClient.java @@ -71,8 +71,14 @@ public InAppWebViewClient(InAppBrowserDelegate inAppBrowserDelegate) { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResourceRequest request) { InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, request.getUrl().toString())) { + // Allow the request synchronously. + return false; + } + if (webView.customSettings.useShouldOverrideUrlLoading) { boolean isRedirect = false; if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT)) { @@ -88,39 +94,54 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request request.isForMainFrame(), request.hasGesture(), isRedirect); - if (webView.regexToCancelSubFramesLoadingCompiled != null) { - if (request.isForMainFrame()) - return true; - else { - Matcher m = webView.regexToCancelSubFramesLoadingCompiled.matcher(request.getUrl().toString()); - return m.matches(); - } - } else { - // There isn't any way to load an URL for a frame that is not the main frame, - // so if the request is not for the main frame, the navigation is allowed. - return request.isForMainFrame(); - } } + if (webView.customSettings.regexToCancelSubFramesLoading != null && !request.isForMainFrame()) { + Matcher m = webView.customSettings.regexToCancelSubFramesLoading.matcher(request.getUrl().toString()); + return m.matches(); + } + if (webView.customSettings.useShouldOverrideUrlLoading) { + // There isn't any way to load an URL for a frame that is not the main frame, + // so if the request is not for the main frame, the navigation is allowed. + return request.isForMainFrame(); + } + return false; } @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - InAppWebView inAppWebView = (InAppWebView) webView; - if (inAppWebView.customSettings.useShouldOverrideUrlLoading) { - onShouldOverrideUrlLoading(inAppWebView, url, "GET", null,true, false, false); + public boolean shouldOverrideUrlLoading(WebView view, String url) { + InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, url)) { + // Allow the request synchronously. + return false; + } + + if (webView.customSettings.useShouldOverrideUrlLoading) { + onShouldOverrideUrlLoading(webView, url, "GET", null,true, false, false); return true; } return false; } + private boolean allowSyncUrlLoading(InAppWebView webView, String url) { + if (webView.customSettings.regexToAllowSyncUrlLoading != null) { + Matcher m = webView.customSettings.regexToAllowSyncUrlLoading.matcher(url); + if (m.matches()) { + Log.d(LOG_TAG, "Request '" + url + "' automatically allowed as it is a match for 'regexToAllowSyncUrlLoading'."); + return true; + } + } + return false; + } + private void allowShouldOverrideUrlLoading(WebView webView, String url, @Nullable Map headers, boolean isForMainFrame) { if (isForMainFrame) { // There isn't any way to load an URL for a frame that is not the main frame, // so call this only on main frame. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && headers != null) webView.loadUrl(url, headers); else webView.loadUrl(url); @@ -164,7 +185,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.shouldOverrideUrlLoading(navigationAction, callback); } else { @@ -178,23 +199,16 @@ public void loadCustomJavaScriptOnPageStarted(WebView view) { if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { String source = webView.userContentController.generateWrappedCodeForDocumentStart(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); - } + webView.evaluateJavascript(source, (ValueCallback) null); } } public void loadCustomJavaScriptOnPageFinished(WebView view) { InAppWebView webView = (InAppWebView) view; - String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); } } @@ -216,7 +230,7 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { webView.channelDelegate.onLoadStart(url); } } - + public void onPageFinished(WebView view, String url) { final InAppWebView webView = (InAppWebView) view; webView.isLoading = false; @@ -237,13 +251,8 @@ public void onPageFinished(WebView view, String url) { CookieSyncManager.getInstance().sync(); } - String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(js, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); - } + String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE(); + webView.evaluateJavascript(js, (ValueCallback) null); if (webView.channelDelegate != null) { webView.channelDelegate.onLoadStop(url); @@ -260,13 +269,13 @@ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (inAppBrowserDelegate != null) { inAppBrowserDelegate.didUpdateVisitedHistory(url); } - + final InAppWebView webView = (InAppWebView) view; if (webView.channelDelegate != null) { webView.channelDelegate.onUpdateVisitedHistory(url, isReload); } } - + @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onReceivedError(WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceError error) { @@ -369,7 +378,7 @@ public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler credentialsProposed = CredentialDatabase.getInstance(view.getContext()).getHttpAuthCredentials(host, protocol, realm, port); URLCredential credentialProposed = null; - if (credentialsProposed != null && credentialsProposed.size() > 0) { + if (credentialsProposed != null && !credentialsProposed.isEmpty()) { credentialProposed = credentialsProposed.get(0); } @@ -396,7 +405,7 @@ public boolean nonNullSuccess(@NonNull HttpAuthResponse response) { handler.proceed(username, password); break; case 2: - if (credentialsProposed.size() > 0) { + if (!credentialsProposed.isEmpty()) { URLCredential credential = credentialsProposed.remove(0); handler.proceed(credential.getUsername(), credential.getPassword()); } else { @@ -429,7 +438,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedHttpAuthRequest(challenge, callback); } else { @@ -488,7 +497,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedServerTrustAuthRequest(challenge, callback); } else { @@ -528,7 +537,7 @@ public boolean nonNullSuccess(@NonNull ClientCertResponse response) { String certificatePath = (String) response.getCertificatePath(); String certificatePassword = (String) response.getCertificatePassword(); String keyStoreType = (String) response.getKeyStoreType(); - Util.PrivateKeyAndCertificates privateKeyAndCertificates = + Util.PrivateKeyAndCertificates privateKeyAndCertificates = Util.loadPrivateKeyAndCertificate(webView.plugin, certificatePath, certificatePassword, keyStoreType); if (privateKeyAndCertificates != null) { request.proceed(privateKeyAndCertificates.privateKey, privateKeyAndCertificates.certificates); @@ -707,7 +716,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque } WebResourceResponse response = null; - if (webView.contentBlockerHandler.getRuleList().size() > 0) { + if (!webView.contentBlockerHandler.getRuleList().isEmpty()) { try { response = webView.contentBlockerHandler.checkUrl(webView, request); } catch (Exception e) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java index f00af4149..1326d6915 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewClientCompat.java @@ -73,6 +73,12 @@ public InAppWebViewClientCompat(InAppBrowserDelegate inAppBrowserDelegate) { @Override public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResourceRequest request) { InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, request.getUrl().toString())) { + // Allow the request synchronously. + return false; + } + if (webView.customSettings.useShouldOverrideUrlLoading) { boolean isRedirect = false; if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT)) { @@ -88,39 +94,54 @@ public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResou request.isForMainFrame(), request.hasGesture(), isRedirect); - if (webView.regexToCancelSubFramesLoadingCompiled != null) { - if (request.isForMainFrame()) - return true; - else { - Matcher m = webView.regexToCancelSubFramesLoadingCompiled.matcher(request.getUrl().toString()); - return m.matches(); - } - } else { - // There isn't any way to load an URL for a frame that is not the main frame, - // so if the request is not for the main frame, the navigation is allowed. - return request.isForMainFrame(); - } } + if (webView.customSettings.regexToCancelSubFramesLoading != null && !request.isForMainFrame()) { + Matcher m = webView.customSettings.regexToCancelSubFramesLoading.matcher(request.getUrl().toString()); + return m.matches(); + } + if (webView.customSettings.useShouldOverrideUrlLoading) { + // There isn't any way to load an URL for a frame that is not the main frame, + // so if the request is not for the main frame, the navigation is allowed. + return request.isForMainFrame(); + } + return false; } @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - InAppWebView inAppWebView = (InAppWebView) webView; - if (inAppWebView.customSettings.useShouldOverrideUrlLoading) { - onShouldOverrideUrlLoading(inAppWebView, url, "GET", null,true, false, false); + public boolean shouldOverrideUrlLoading(WebView view, String url) { + InAppWebView webView = (InAppWebView) view; + + if (allowSyncUrlLoading(webView, url)) { + // Allow the request synchronously. + return false; + } + + if (webView.customSettings.useShouldOverrideUrlLoading) { + onShouldOverrideUrlLoading(webView, url, "GET", null,true, false, false); return true; } return false; } + private boolean allowSyncUrlLoading(InAppWebView webView, String url) { + if (webView.customSettings.regexToAllowSyncUrlLoading != null) { + Matcher m = webView.customSettings.regexToAllowSyncUrlLoading.matcher(url); + if (m.matches()) { + Log.d(LOG_TAG, "Request '" + url + "' automatically allowed as it is a match for 'regexToAllowSyncUrlLoading'."); + return true; + } + } + return false; + } + private void allowShouldOverrideUrlLoading(WebView webView, String url, @Nullable Map headers, boolean isForMainFrame) { if (isForMainFrame) { // There isn't any way to load an URL for a frame that is not the main frame, // so call this only on main frame. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && headers != null) webView.loadUrl(url, headers); else webView.loadUrl(url); @@ -164,7 +185,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.shouldOverrideUrlLoading(navigationAction, callback); } else { @@ -178,23 +199,16 @@ public void loadCustomJavaScriptOnPageStarted(WebView view) { if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { String source = webView.userContentController.generateWrappedCodeForDocumentStart(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); - } + webView.evaluateJavascript(source, (ValueCallback) null); } } public void loadCustomJavaScriptOnPageFinished(WebView view) { InAppWebView webView = (InAppWebView) view; - String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = webView.userContentController.generateWrappedCodeForDocumentEnd(); webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); } } @@ -216,7 +230,7 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { webView.channelDelegate.onLoadStart(url); } } - + public void onPageFinished(WebView view, String url) { final InAppWebView webView = (InAppWebView) view; webView.isLoading = false; @@ -237,13 +251,8 @@ public void onPageFinished(WebView view, String url) { CookieSyncManager.getInstance().sync(); } - String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(js, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); - } + String js = JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE(); + webView.evaluateJavascript(js, (ValueCallback) null); if (webView.channelDelegate != null) { webView.channelDelegate.onLoadStop(url); @@ -260,13 +269,13 @@ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (inAppBrowserDelegate != null) { inAppBrowserDelegate.didUpdateVisitedHistory(url); } - + final InAppWebView webView = (InAppWebView) view; if (webView.channelDelegate != null) { webView.channelDelegate.onUpdateVisitedHistory(url, isReload); } } - + @RequiresApi(api = Build.VERSION_CODES.M) @Override public void onReceivedError(@NonNull WebView view, @@ -381,7 +390,7 @@ public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler credentialsProposed = CredentialDatabase.getInstance(view.getContext()).getHttpAuthCredentials(host, protocol, realm, port); URLCredential credentialProposed = null; - if (credentialsProposed != null && credentialsProposed.size() > 0) { + if (credentialsProposed != null && !credentialsProposed.isEmpty()) { credentialProposed = credentialsProposed.get(0); } @@ -408,7 +417,7 @@ public boolean nonNullSuccess(@NonNull HttpAuthResponse response) { handler.proceed(username, password); break; case 2: - if (credentialsProposed.size() > 0) { + if (!credentialsProposed.isEmpty()) { URLCredential credential = credentialsProposed.remove(0); handler.proceed(credential.getUsername(), credential.getPassword()); } else { @@ -441,7 +450,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedHttpAuthRequest(challenge, callback); } else { @@ -500,7 +509,7 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - + if (webView.channelDelegate != null) { webView.channelDelegate.onReceivedServerTrustAuthRequest(challenge, callback); } else { @@ -540,7 +549,7 @@ public boolean nonNullSuccess(@NonNull ClientCertResponse response) { String certificatePath = (String) response.getCertificatePath(); String certificatePassword = (String) response.getCertificatePassword(); String keyStoreType = (String) response.getKeyStoreType(); - Util.PrivateKeyAndCertificates privateKeyAndCertificates = + Util.PrivateKeyAndCertificates privateKeyAndCertificates = Util.loadPrivateKeyAndCertificate(webView.plugin, certificatePath, certificatePassword, keyStoreType); if (privateKeyAndCertificates != null) { request.proceed(privateKeyAndCertificates.privateKey, privateKeyAndCertificates.certificates); @@ -734,7 +743,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque } WebResourceResponse response = null; - if (webView.contentBlockerHandler.getRuleList().size() > 0) { + if (!webView.contentBlockerHandler.getRuleList().isEmpty()) { try { response = webView.contentBlockerHandler.checkUrl(webView, request); } catch (Exception e) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java index 91c13ea36..9a157f981 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; public class InAppWebViewSettings implements ISettings { @@ -48,6 +49,8 @@ public class InAppWebViewSettings implements ISettings { public List>> contentBlockers = new ArrayList<>(); public Integer preferredContentMode = PreferredContentModeOptionType.RECOMMENDED.toValue(); public Boolean useShouldInterceptAjaxRequest = false; + public Boolean useOnAjaxReadyStateChange = false; + public Boolean useOnAjaxProgress = false; public Boolean interceptOnlyAsyncAjaxRequests = true; public Boolean useShouldInterceptFetchRequest = false; public Boolean incognito = false; @@ -60,7 +63,8 @@ public class InAppWebViewSettings implements ISettings { public Boolean allowFileAccessFromFileURLs = false; public Boolean allowUniversalAccessFromFileURLs = false; public Boolean allowBackgroundAudioPlaying = false; - public Integer textZoom = 100; + @Nullable + public Integer textZoom; /** * @deprecated */ @@ -72,9 +76,11 @@ public class InAppWebViewSettings implements ISettings { public Boolean domStorageEnabled = true; public Boolean useWideViewPort = true; public Boolean safeBrowsingEnabled = true; + @Nullable public Integer mixedContentMode; public Boolean allowContentAccess = true; public Boolean allowFileAccess = true; + @Nullable public String appCachePath; public Boolean blockNetworkImage = false; public Boolean blockNetworkLoads = false; @@ -86,8 +92,10 @@ public class InAppWebViewSettings implements ISettings { public Integer disabledActionModeMenuItems; public String fantasyFontFamily = "fantasy"; public String fixedFontFamily = "monospace"; - public Integer forceDark = 0; // WebSettingsCompat.FORCE_DARK_OFF - public Integer forceDarkStrategy = WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING; + @Nullable @Deprecated + public Integer forceDark; + @Nullable @Deprecated + public Integer forceDarkStrategy; public Boolean geolocationEnabled = true; public WebSettings.LayoutAlgorithm layoutAlgorithm; public Boolean loadWithOverviewMode = true; @@ -103,16 +111,22 @@ public class InAppWebViewSettings implements ISettings { public Boolean thirdPartyCookiesEnabled = true; public Boolean hardwareAcceleration = true; public Boolean supportMultipleWindows = false; - public String regexToCancelSubFramesLoading; + @Nullable + public Pattern regexToCancelSubFramesLoading; + @Nullable + public Pattern regexToAllowSyncUrlLoading; public Integer overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS; - public Boolean networkAvailable = null; + @Nullable + public Boolean networkAvailable; public Integer scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY; public Integer verticalScrollbarPosition = View.SCROLLBAR_POSITION_DEFAULT; - public Integer scrollBarDefaultDelayBeforeFade = null; + @Nullable + public Integer scrollBarDefaultDelayBeforeFade; public Boolean scrollbarFadingEnabled = true; - public Integer scrollBarFadeDuration = null; @Nullable - public Map rendererPriorityPolicy = null; + public Integer scrollBarFadeDuration; + @Nullable + public Map rendererPriorityPolicy; public Boolean useShouldInterceptRequest = false; public Boolean useOnRenderProcessGone = false; public Boolean disableDefaultErrorPage = false; @@ -133,6 +147,21 @@ public class InAppWebViewSettings implements ISettings { public byte[] defaultVideoPoster; @Nullable public Set requestedWithHeaderOriginAllowList; + @Nullable + public Set javaScriptHandlersOriginAllowList; + public Boolean javaScriptHandlersForMainFrameOnly = false; + public Boolean javaScriptBridgeEnabled = true; + @Nullable + public Set javaScriptBridgeOriginAllowList; + @Nullable + public Boolean javaScriptBridgeForMainFrameOnly; + @Nullable + public Set pluginScriptsOriginAllowList; + public Boolean pluginScriptsForMainFrameOnly = false; + public Boolean isUserInteractionEnabled = true; + @Nullable + public Double alpha; + public Boolean useOnShowFileChooser = false; @NonNull @Override @@ -193,6 +222,12 @@ public InAppWebViewSettings parse(@NonNull Map settings) { case "useShouldInterceptAjaxRequest": useShouldInterceptAjaxRequest = (Boolean) value; break; + case "useOnAjaxReadyStateChange": + useOnAjaxReadyStateChange = (Boolean) value; + break; + case "useOnAjaxProgress": + useOnAjaxProgress = (Boolean) value; + break; case "interceptOnlyAsyncAjaxRequests": interceptOnlyAsyncAjaxRequests = (Boolean) value; break; @@ -344,7 +379,10 @@ public InAppWebViewSettings parse(@NonNull Map settings) { supportMultipleWindows = (Boolean) value; break; case "regexToCancelSubFramesLoading": - regexToCancelSubFramesLoading = (String) value; + regexToCancelSubFramesLoading = Pattern.compile((String) value); + break; + case "regexToAllowSyncUrlLoading": + regexToAllowSyncUrlLoading = Pattern.compile((String) value); break; case "overScrollMode": overScrollMode = (Integer) value; @@ -412,6 +450,39 @@ public InAppWebViewSettings parse(@NonNull Map settings) { case "requestedWithHeaderOriginAllowList": requestedWithHeaderOriginAllowList = new HashSet<>((List) value); break; + case "javaScriptHandlersOriginAllowList": + javaScriptHandlersOriginAllowList = new HashSet<>(); + for (String pattern : (List) value) { + javaScriptHandlersOriginAllowList.add(Pattern.compile(pattern)); + } + break; + case "javaScriptHandlersForMainFrameOnly": + javaScriptHandlersForMainFrameOnly = (Boolean) value; + break; + case "javaScriptBridgeEnabled": + javaScriptBridgeEnabled = (Boolean) value; + break; + case "javaScriptBridgeOriginAllowList": + javaScriptBridgeOriginAllowList = new HashSet<>((List) value); + break; + case "javaScriptBridgeForMainFrameOnly": + javaScriptBridgeForMainFrameOnly = (Boolean) value; + break; + case "pluginScriptsOriginAllowList": + pluginScriptsOriginAllowList = new HashSet<>((List) value); + break; + case "pluginScriptsForMainFrameOnly": + pluginScriptsForMainFrameOnly = (Boolean) value; + break; + case "isUserInteractionEnabled": + isUserInteractionEnabled = (Boolean) value; + break; + case "alpha": + alpha = (Double) value; + break; + case "useOnShowFileChooser": + useOnShowFileChooser = (Boolean) value; + break; } } @@ -438,6 +509,8 @@ public Map toMap() { settings.put("contentBlockers", contentBlockers); settings.put("preferredContentMode", preferredContentMode); settings.put("useShouldInterceptAjaxRequest", useShouldInterceptAjaxRequest); + settings.put("useOnAjaxReadyStateChange", useOnAjaxReadyStateChange); + settings.put("useOnAjaxProgress", useOnAjaxProgress); settings.put("interceptOnlyAsyncAjaxRequests", interceptOnlyAsyncAjaxRequests); settings.put("useShouldInterceptFetchRequest", useShouldInterceptFetchRequest); settings.put("incognito", incognito); @@ -488,7 +561,8 @@ public Map toMap() { settings.put("thirdPartyCookiesEnabled", thirdPartyCookiesEnabled); settings.put("hardwareAcceleration", hardwareAcceleration); settings.put("supportMultipleWindows", supportMultipleWindows); - settings.put("regexToCancelSubFramesLoading", regexToCancelSubFramesLoading); + settings.put("regexToCancelSubFramesLoading", regexToCancelSubFramesLoading != null ? regexToCancelSubFramesLoading.pattern() : null); + settings.put("regexToAllowSyncUrlLoading", regexToAllowSyncUrlLoading != null ? regexToAllowSyncUrlLoading.pattern() : null); settings.put("overScrollMode", overScrollMode); settings.put("networkAvailable", networkAvailable); settings.put("scrollBarStyle", scrollBarStyle); @@ -511,6 +585,23 @@ public Map toMap() { settings.put("defaultVideoPoster", defaultVideoPoster); settings.put("requestedWithHeaderOriginAllowList", requestedWithHeaderOriginAllowList != null ? new ArrayList<>(requestedWithHeaderOriginAllowList) : null); + settings.put("javaScriptHandlersOriginAllowList", + javaScriptHandlersOriginAllowList != null ? new ArrayList() {{ + for (Pattern pattern : javaScriptHandlersOriginAllowList) { + add(pattern.pattern()); + } + }} : null); + settings.put("javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly); + settings.put("javaScriptBridgeEnabled", javaScriptBridgeEnabled); + settings.put("javaScriptBridgeOriginAllowList", + javaScriptBridgeOriginAllowList != null ? new ArrayList<>(javaScriptBridgeOriginAllowList) : null); + settings.put("javaScriptBridgeForMainFrameOnly", javaScriptBridgeForMainFrameOnly); + settings.put("pluginScriptsOriginAllowList", + pluginScriptsOriginAllowList != null ? new ArrayList<>(pluginScriptsOriginAllowList) : null); + settings.put("pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly); + settings.put("isUserInteractionEnabled", isUserInteractionEnabled); + settings.put("alpha", alpha); + settings.put("useOnShowFileChooser", useOnShowFileChooser); return settings; } @@ -521,6 +612,8 @@ public Map getRealSettings(@NonNull InAppWebViewInterface inAppW Map realSettings = toMap(); if (inAppWebView instanceof InAppWebView) { InAppWebView webView = (InAppWebView) inAppWebView; + realSettings.put("alpha", webView.getAlpha()); + WebSettings settings = webView.getSettings(); realSettings.put("userAgent", settings.getUserAgentString()); realSettings.put("javaScriptEnabled", settings.getJavaScriptEnabled()); @@ -557,7 +650,8 @@ public Map getRealSettings(@NonNull InAppWebViewInterface inAppW realSettings.put("defaultTextEncodingName", settings.getDefaultTextEncodingName()); if (WebViewFeature.isFeatureSupported(WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS)) { realSettings.put("disabledActionModeMenuItems", WebSettingsCompat.getDisabledActionModeMenuItems(settings)); - } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { realSettings.put("disabledActionModeMenuItems", settings.getDisabledActionModeMenuItems()); } realSettings.put("fantasyFontFamily", settings.getFantasyFontFamily()); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java index b7d059c27..54230d09c 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageChannel.java @@ -56,7 +56,7 @@ public void initJsInstance(InAppWebViewInterface webView, final ValueCallback() { @Override public void onReceiveValue(String value) { diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java index fe7326f70..164e3d3db 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/web_message/WebMessageListener.java @@ -91,7 +91,7 @@ public void initJsInstance() { " var scheme = !isPageBlank ? window.location.protocol.replace(':', '') : null;" + " var host = !isPageBlank ? window.location.hostname : null;" + " var port = !isPageBlank ? window.location.port : null;" + - " if (window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._isOriginAllowed(allowedOriginRules, scheme, host, port)) {" + + " if (window." + JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME() + "._isOriginAllowed(allowedOriginRules, scheme, host, port)) {" + " window['" + jsObjectNameEscaped + "'] = new FlutterInAppWebViewWebMessageListener('" + jsObjectNameEscaped + "');" + " }" + "})();"; @@ -101,7 +101,8 @@ public void initJsInstance() { UserScriptInjectionTime.AT_DOCUMENT_START, null, false, - null + webView.getCustomSettings().pluginScriptsOriginAllowList, + webView.getCustomSettings().pluginScriptsForMainFrameOnly )); } } diff --git a/flutter_inappwebview_android/example/pubspec.lock b/flutter_inappwebview_android/example/pubspec.lock index 8603227e7..27dad23d8 100644 --- a/flutter_inappwebview_android/example/pubspec.lock +++ b/flutter_inappwebview_android/example/pubspec.lock @@ -81,23 +81,22 @@ packages: path: ".." relative: true source: path - version: "1.1.3" + version: "1.2.0-beta.3" flutter_inappwebview_internal_annotations: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "6862f4e08aa8f6136762e022c9c1edafb18c1dc3beb03052f2f3f2a48605a182" - url: "https://pub.dev" - source: hosted - version: "1.3.0" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: diff --git a/flutter_inappwebview_android/lib/src/cookie_manager.dart b/flutter_inappwebview_android/lib/src/cookie_manager.dart index c0e2fba5e..86ae5fe81 100755 --- a/flutter_inappwebview_android/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_android/lib/src/cookie_manager.dart @@ -209,6 +209,12 @@ class AndroidCookieManager extends PlatformCookieManager false; } + @override + Future flush() async { + Map args = {}; + await channel?.invokeMethod('flush', args); + } + @override void dispose() { // empty diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart index c53a36d57..3a057c971 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,8 +1,7 @@ -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; import 'in_app_webview_controller.dart'; @@ -32,8 +31,10 @@ class AndroidHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -150,6 +151,7 @@ class AndroidHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -360,13 +362,24 @@ class AndroidHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { @@ -384,6 +397,10 @@ class AndroidHeadlessInAppWebView extends PlatformHeadlessInAppWebView settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart index e96a92b78..a585ae398 100755 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart @@ -1,16 +1,15 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; -import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// @@ -39,8 +38,10 @@ class AndroidInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -126,6 +127,7 @@ class AndroidInAppWebViewWidgetCreationParams super.onCameraCaptureStateChanged, super.onMicrophoneCaptureStateChanged, super.onContentSizeChanged, + super.onShowFileChooser, super.initialUrlRequest, super.initialFile, super.initialData, @@ -163,6 +165,7 @@ class AndroidInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -243,6 +246,7 @@ class AndroidInAppWebViewWidgetCreationParams onMicrophoneCaptureStateChanged: params.onMicrophoneCaptureStateChanged, onContentSizeChanged: params.onContentSizeChanged, + onShowFileChooser: params.onShowFileChooser, initialUrlRequest: params.initialUrlRequest, initialFile: params.initialFile, initialData: params.initialData, @@ -422,15 +426,24 @@ class AndroidInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { @@ -448,6 +461,10 @@ class AndroidInAppWebViewWidget extends PlatformInAppWebViewWidget { settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart index ceb4bc9a9..38c4ca88c 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,19 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; - import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [AndroidInAppWebViewController]. /// @@ -66,8 +46,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; // List of properties to be saved and restored for keep alive feature - Map _javaScriptHandlersMap = - HashMap(); + Map _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -363,11 +342,11 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -375,20 +354,25 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1406,19 +1390,42 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController .onContentSizeChanged(oldContentSize, newContentSize); } break; + case "onShowFileChooser": + if ((webviewParams != null && + webviewParams!.onShowFileChooser != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + ShowFileChooserRequest request = + ShowFileChooserRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onShowFileChooser != null) + return (await webviewParams!.onShowFileChooser!( + _controllerFromPlatform, request)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler!.onShowFileChooser(request)) + ?.toMap(); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1440,7 +1447,8 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1457,43 +1465,46 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! - .onAjaxProgress(request)) - ?.toNativeValue(); + return jsonEncode( + (await _inAppBrowserEventHandler!.onAjaxProgress(request)) + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1519,7 +1530,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1527,7 +1538,7 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1539,7 +1550,19 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + var jsHandlerResult = null; + if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerCallback) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerCallback)(handlerData.args); + } else if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerFunction) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerFunction)(handlerData); + } else { + jsHandlerResult = await _javaScriptHandlersMap[handlerName]!(); + } + return jsonEncode(jsHandlerResult); } catch (error, stacktrace) { developer.log(error.toString() + '\n' + stacktrace.toString(), name: 'JavaScript Handler "$handlerName"'); @@ -1986,16 +2009,14 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2241,12 +2262,35 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController return InAppWebViewHitTestResult(type: type, extra: extra); } + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + @override Future clearFocus() async { Map args = {}; return await channel?.invokeMethod('clearFocus', args); } + @override + Future showInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('showInputMethod', args); + } + + @override + Future hideInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('hideInputMethod', args); + } + @override Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; @@ -2648,6 +2692,19 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('clearFormData', args); } + @override + Future saveState() async { + Map args = {}; + return await channel?.invokeMethod('saveState', args); + } + + @override + Future restoreState(Uint8List? state) async { + Map args = {}; + args.putIfAbsent('state', () => state); + return await channel?.invokeMethod('restoreState', args) ?? false; + } + @override Future getDefaultUserAgent() async { Map args = {}; @@ -2738,6 +2795,29 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController await _staticChannel.invokeMethod('clearAllCache', args); } + @override + Future enableSlowWholeDocumentDraw() async { + Map args = {}; + await _staticChannel.invokeMethod('enableSlowWholeDocumentDraw', args); + } + + @override + Future setJavaScriptBridgeName(String bridgeName) async { + assert(RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName), + 'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.'); + Map args = {}; + args.putIfAbsent('bridgeName', () => bridgeName); + await _staticChannel.invokeMethod('setJavaScriptBridgeName', args); + } + + @override + Future getJavaScriptBridgeName() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getJavaScriptBridgeName', args) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart index 768311549..cc92868d0 100644 --- a/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_android/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class AndroidPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [AndroidPrintJobControllerCreationParams] instance. const AndroidPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [AndroidPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory AndroidPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class AndroidPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return AndroidPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -43,6 +43,11 @@ class AndroidPrintJobController extends PlatformPrintJobController Future _handleMethod(MethodCall call) async { switch (call.method) { + case "onComplete": + bool completed = call.arguments["completed"]; + String? error = call.arguments["error"]; + onComplete?.call(completed, error); + break; default: throw UnimplementedError("Unimplemented ${call.method} method"); } diff --git a/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart index f8979f5cf..a6c0b9691 100644 --- a/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_android/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class AndroidWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_android/lib/src/webview_asset_loader.dart b/flutter_inappwebview_android/lib/src/webview_asset_loader.dart index 64ca3ec35..8c3d0320f 100644 --- a/flutter_inappwebview_android/lib/src/webview_asset_loader.dart +++ b/flutter_inappwebview_android/lib/src/webview_asset_loader.dart @@ -54,7 +54,7 @@ abstract mixin class AndroidPathHandler } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {"path": path, "type": type, "id": _id}; } @@ -194,7 +194,7 @@ class AndroidInternalStoragePathHandler String get directory => _internalParams.directory; @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {...toMap(), 'directory': directory}; } } diff --git a/flutter_inappwebview_android/pubspec.yaml b/flutter_inappwebview_android/pubspec.yaml index 912f92eef..801a592ce 100644 --- a/flutter_inappwebview_android/pubspec.yaml +++ b/flutter_inappwebview_android/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_android description: Android implementation of the flutter_inappwebview plugin. -version: 1.1.3 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_android issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,7 +20,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.3.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_ios/CHANGELOG.md b/flutter_inappwebview_ios/CHANGELOG.md index 60e2bffdb..5bbf30252 100644 --- a/flutter_inappwebview_ios/CHANGELOG.md +++ b/flutter_inappwebview_ios/CHANGELOG.md @@ -1,3 +1,31 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Merged "Add proxy support for iOS" [#2362](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2362) (thanks to [yerkejs](https://github.com/yerkejs)) +- Fixed "[iOS] Webview opened with windowId does not receive javascript handler callback." [#2393](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2393) +- Fixed internal javascript callback handlers when the WebView has windowId not null +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `setInputMethodEnabled`, `hideInputMethod` InAppWebViewController methods +- Implemented `isUserInteractionEnabled`, `alpha` properties of `InAppWebViewSettings` +- Merged "Show / Hide / Disable / Enable soft Keyboard Input (Android & iOS)" [#2408](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2408) (thanks to [Mecharyry](https://github.com/Mecharyry)) +- Fixed "In iOS version 17.2, when moving the input focus in a WebView, an unknown area appears at the top of the screen." [#1947](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947) + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Implemented `requestFocus` WebView method +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Added support for `UserScript.allowedOriginRules` parameter +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- Fixed `show`, `hide` methods and `hidden` setting for `InAppBrowser` + ## 1.1.2 - Updated flutter_inappwebview_platform_interface version to ^1.3.0 diff --git a/flutter_inappwebview_ios/example/pubspec.lock b/flutter_inappwebview_ios/example/pubspec.lock index 2eb184fac..621957ae2 100644 --- a/flutter_inappwebview_ios/example/pubspec.lock +++ b/flutter_inappwebview_ios/example/pubspec.lock @@ -79,25 +79,24 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_ios: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.1.2" + version: "1.2.0-beta.3" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "6862f4e08aa8f6136762e022c9c1edafb18c1dc3beb03052f2f3f2a48605a182" - url: "https://pub.dev" - source: hosted - version: "1.3.0" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: diff --git a/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift b/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift index bc6602441..f76e4fbd2 100755 --- a/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift +++ b/flutter_inappwebview_ios/ios/Classes/CredentialDatabase.swift @@ -14,7 +14,7 @@ public class CredentialDatabase: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift b/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift index cff9cc9e6..0c4a8723c 100644 --- a/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift +++ b/flutter_inappwebview_ios/ios/Classes/FindInteraction/FindInteractionController.swift @@ -54,11 +54,9 @@ public class FindInteractionController: NSObject, Disposable { self.plugin = plugin self.webView = webView self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) } public func prepare() { @@ -99,7 +97,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else if find != "" { - let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');" + let startSearch = "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync('\(find)');" webView.evaluateJavaScript(startSearch, completionHandler: completionHandler) } } @@ -123,7 +121,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else { - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) } } @@ -143,7 +141,7 @@ public class FindInteractionController: NSObject, Disposable { completionHandler(nil, nil) } } else { - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches();", completionHandler: completionHandler) } } diff --git a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index 5d13086d7..dffc124da 100644 --- a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -20,7 +20,7 @@ public class HeadlessInAppWebView: Disposable { self.flutterWebView = flutterWebView self.plugin = plugin let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel) } diff --git a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 36d770c36..f0af5906d 100644 --- a/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -19,7 +19,7 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift index 06219762e..0be2b5734 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -17,11 +17,11 @@ public class InAppBrowserManager: ChannelDelegate { static let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" static let NAV_STORYBOARD_CONTROLLER_ID = "navController" var plugin: SwiftFlutterPlugin? - - private var previousStatusBarStyle = -1 + + var navControllers: [String: InAppBrowserNavigationController?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -44,10 +44,6 @@ public class InAppBrowserManager: ChannelDelegate { } public func prepareInAppBrowserWebViewController(settings: [String: Any?]) -> InAppBrowserWebViewController { - if previousStatusBarStyle == -1 { - previousStatusBarStyle = UIApplication.shared.statusBarStyle.rawValue - } - let browserSettings = InAppBrowserSettings() let _ = browserSettings.parse(settings: settings) @@ -59,7 +55,6 @@ public class InAppBrowserManager: ChannelDelegate { webViewController.browserSettings = browserSettings webViewController.isHidden = browserSettings.hidden webViewController.webViewSettings = webViewSettings - webViewController.previousStatusBarStyle = previousStatusBarStyle return webViewController } @@ -105,17 +100,12 @@ public class InAppBrowserManager: ChannelDelegate { navController.pushViewController(webViewController, animated: false) webViewController.prepareNavigationControllerBeforeViewWillAppear() - var animated = true - if let browserSettings = webViewController.browserSettings, browserSettings.hidden { - animated = false - } - guard let visibleViewController = UIApplication.shared.visibleViewController else { assertionFailure("Failure init the visibleViewController!") return } - - if let popover = navController.popoverPresentationController { + + if let popover = webViewController.popoverPresentationController { let sourceView = visibleViewController.view ?? UIView() popover.sourceRect = CGRect(x: sourceView.bounds.midX, y: sourceView.bounds.midY, width: 0, height: 0) @@ -123,7 +113,13 @@ public class InAppBrowserManager: ChannelDelegate { popover.sourceView = sourceView } - visibleViewController.present(navController, animated: animated) + if let browserSettings = webViewController.browserSettings, browserSettings.hidden { + webViewController.loadViewIfNeeded() + } else { + visibleViewController.present(navController, animated: true) + } + + navControllers[webViewController.id] = navController } public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) { @@ -144,6 +140,11 @@ public class InAppBrowserManager: ChannelDelegate { public override func dispose() { super.dispose() + let navControllersValues = navControllers.values + navControllersValues.forEach { (navController: InAppBrowserNavigationController?) in + navController?.dismiss(animated: false) + } + navControllers.removeAll() plugin = nil } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift index 2d7b8c590..1ab8a3642 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserNavigationController.swift @@ -8,12 +8,8 @@ import Foundation public class InAppBrowserNavigationController: UINavigationController { - var tmpWindow: UIWindow? - deinit { debugPrint("InAppBrowserNavigationController - dealloc") - tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) - tmpWindow = nil UIApplication.shared.delegate?.window??.makeKeyAndVisible() } } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index c75434782..cee45cfa9 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -47,18 +47,17 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega var initialMimeType: String? var initialEncoding: String? var initialBaseUrl: String? - var previousStatusBarStyle = -1 var initialUserScripts: [[String: Any]] = [] var pullToRefreshInitialSettings: [String: Any?] = [:] var isHidden = false var menuItems: [InAppBrowserMenuItem] = [] public override func loadView() { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger()) + let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: plugin.registrar.messenger()) channelDelegate = InAppBrowserChannelDelegate(channel: channel) var userScripts: [UserScript] = [] @@ -214,7 +213,9 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public override func viewDidDisappear(_ animated: Bool) { - dispose() + if !isHidden { + dispose() + } super.viewDidDisappear(animated) } @@ -411,31 +412,27 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func show(completion: (() -> Void)? = nil) { - if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow { + if let visibleViewController = UIApplication.shared.visibleViewController, + let navigationController = navigationController { isHidden = false - window.alpha = 0.0 - window.isHidden = false - window.makeKeyAndVisible() - UIView.animate(withDuration: 0.2) { - window.alpha = 1.0 + visibleViewController.present(navigationController, animated: true) { completion?() } + } else { + completion?() } } public func hide(completion: (() -> Void)? = nil) { - if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow { + if let navigationController = navigationController { isHidden = true - window.alpha = 1.0 - UIView.animate(withDuration: 0.2) { - window.alpha = 0.0 - } completion: { (finished) in - if finished { - window.isHidden = true - UIApplication.shared.delegate?.window??.makeKeyAndVisible() - completion?() - } + navigationController.dismiss(animated: true) { + completion?() + UIApplication.shared.delegate?.window??.makeKeyAndVisible() } + } else { + completion?() + UIApplication.shared.delegate?.window??.makeKeyAndVisible() } } @@ -451,24 +448,31 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega public func close(completion: (() -> Void)? = nil) { if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! { - navigationController?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in + if let presentingViewController = navigationController?.presentingViewController { + presentingViewController.dismiss(animated: true, completion: {() -> Void in + completion?() + self.dispose() + }) + } else { completion?() - }) + dispose() + } } else { - navigationController?.parent?.dismiss(animated: true, completion: {() -> Void in + if let parent = navigationController?.parent { + parent.dismiss(animated: true, completion: {() -> Void in + completion?() + self.dispose() + }) + } else { completion?() - }) + dispose() + } } } @objc public func close() { - if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! { - navigationController?.presentingViewController?.dismiss(animated: true, completion: nil) - } - else { - navigationController?.parent?.dismiss(animated: true, completion: nil) - } + close(completion: nil) } @objc public func goBack() { @@ -633,9 +637,6 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega webView?.removeFromSuperview() webView = nil view = nil - if previousStatusBarStyle != -1, let statusBarStyle = UIStatusBarStyle(rawValue: previousStatusBarStyle) { - UIApplication.shared.statusBarStyle = statusBarStyle - } transitioningDelegate = nil searchBar?.delegate = nil closeButton?.target = nil @@ -644,6 +645,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega reloadButton?.target = nil shareButton?.target = nil menuButton?.target = nil + plugin?.inAppBrowserManager?.navControllers[id] = nil plugin = nil } diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift index 9a0d06902..7a2f1f260 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -44,11 +44,9 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable webView = webViewTransport.webView webView!.id = viewId webView!.plugin = plugin - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger()) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) - } + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: plugin.registrar.messenger()) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) webView!.frame = myView!.bounds webView!.contextMenu = contextMenu webView!.initialUserScripts = userScripts diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift index 43ac03776..52c0bae70 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebView.swift @@ -7,7 +7,7 @@ import Flutter import Foundation -import WebKit +@preconcurrency import WebKit public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, @@ -69,6 +69,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, fileprivate var interceptOnlyAsyncAjaxRequestsPluginScript: PluginScript? + private var exceptedBridgeSecret = NSUUID().uuidString + private var javaScriptBridgeEnabled = true + init(id: Any?, plugin: SwiftFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, contextMenu: [String: Any]?, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) @@ -103,18 +106,43 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, set { super.frame = newValue - self.scrollView.contentInset = .zero + scrollView.contentInset = .zero if #available(iOS 11, *) { // Above iOS 11, adjust contentInset to compensate the adjustedContentInset so the sum will // always be 0. - if (scrollView.adjustedContentInset != UIEdgeInsets.zero) { - let insetToAdjust = self.scrollView.adjustedContentInset + if (scrollView.adjustedContentInset != .zero) { + let insetToAdjust = scrollView.adjustedContentInset + scrollView.contentInset = UIEdgeInsets(top: -insetToAdjust.top, left: -insetToAdjust.left, + bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) + } + } + } + } + + // Fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947 + private var _scrollViewContentInsetAdjusted = false + @objc func keyboardWillShow(notification: NSNotification) { + // UIResponder.keyboardWillShowNotification will be fired also + // when changing focus between HTML inputs with the keyboard already open + if (scrollView.adjustedContentInset != .zero) { + // if resizeToAvoidBottomInset is false on Flutter side, + // scrollView.adjustedContentInset.bottom will be > 0 + if scrollView.adjustedContentInset.bottom > 0 { + // if the scrollView.contentInset has already been fixed, do nothing + if !_scrollViewContentInsetAdjusted { + _scrollViewContentInsetAdjusted = true + let insetToAdjust = scrollView.adjustedContentInset scrollView.contentInset = UIEdgeInsets(top: -insetToAdjust.top, left: -insetToAdjust.left, - bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) + bottom: -insetToAdjust.bottom, right: -insetToAdjust.right) } + } else { + scrollView.contentInset = .zero } } } + @objc func keyboardWillHide(notification: NSNotification) { + _scrollViewContentInsetAdjusted = false + } required public init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! @@ -189,7 +217,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, lastLongPressTouchPoint = touchLocation - evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in + evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in if error != nil { print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")") } else if let value = value as? [String: Any?] { @@ -345,6 +373,16 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } public func prepare() { + if #available(iOS 17.2, *) { + // Fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/1947 + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), + name: UIResponder.keyboardWillShowNotification, + object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + scrollView.addGestureRecognizer(self.longPressRecognizer) scrollView.addGestureRecognizer(self.recognizerForDisablingContextMenuOnLinks) scrollView.addGestureRecognizer(self.panGestureRecognizer) @@ -414,6 +452,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // } if let settings = settings { + isUserInteractionEnabled = settings.isUserInteractionEnabled + + if let viewAlpha = settings.alpha { + alpha = CGFloat(viewAlpha) + } + + javaScriptBridgeEnabled = settings.javaScriptBridgeEnabled + if let javaScriptBridgeOriginAllowList = settings.javaScriptBridgeOriginAllowList, javaScriptBridgeOriginAllowList.isEmpty { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false + } + if settings.transparentBackground { isOpaque = false backgroundColor = UIColor.clear @@ -546,56 +596,62 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // This is a limitation of the official WebKit API. return } - configuration.userContentController = WKUserContentController() configuration.userContentController.initialize() if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { return } - configuration.userContentController.addPluginScript(PROMISE_POLYFILL_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(CONSOLE_LOG_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(PRINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT) - if let settings = settings { - interceptOnlyAsyncAjaxRequestsPluginScript = createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests) - if settings.useShouldInterceptAjaxRequest { - if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + let pluginScriptsOriginAllowList = settings?.pluginScriptsOriginAllowList + let pluginScriptsForMainFrameOnly = settings?.pluginScriptsForMainFrameOnly ?? true + + let javaScriptBridgeOriginAllowList = settings?.javaScriptBridgeOriginAllowList ?? pluginScriptsOriginAllowList + let javaScriptBridgeForMainFrameOnly = settings?.javaScriptBridgeForMainFrameOnly ?? pluginScriptsForMainFrameOnly + + configuration.userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: exceptedBridgeSecret, allowedOriginRules: javaScriptBridgeOriginAllowList, forMainFrameOnly: javaScriptBridgeForMainFrameOnly)) + configuration.userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindElementsAtPointJS.FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(LastTouchedAnchorOrImageJS.LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindTextHighlightJS.FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OriginalViewPortMetaTagContentJS.ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + if let settings = settings { + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests, + allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly) + if settings.useShouldInterceptAjaxRequest { + if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { + configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + } + configuration.userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, + forMainFrameOnly: pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: settings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: settings.useOnAjaxProgress)) + } + if settings.useShouldInterceptFetchRequest { + configuration.userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if settings.useOnLoadResource { + configuration.userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if !settings.supportZoom { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + } else if settings.enableViewportScale { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) } - configuration.userContentController.addPluginScript(INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useShouldInterceptFetchRequest { - configuration.userContentController.addPluginScript(INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useOnLoadResource { - configuration.userContentController.addPluginScript(ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) - } - if !settings.supportZoom { - configuration.userContentController.addPluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - } else if settings.enableViewportScale { - configuration.userContentController.addPluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) } } - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") - configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived") configuration.userContentController.addUserOnlyScripts(initialUserScripts) configuration.userContentController.sync(scriptMessageHandler: self) } public static func preWKWebViewConfiguration(settings: InAppWebViewSettings?) -> WKWebViewConfiguration { let configuration = WKWebViewConfiguration() - + // initialzie WKUserContentController here to fix possible "undefined is not an object (evaluating 'window.webkit.messageHandlers')" javascript error + configuration.userContentController = WKUserContentController() configuration.processPool = WKProcessPoolManager.sharedProcessPool if let settings = settings { @@ -683,7 +739,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if let lastLongPressTouhLocation = lastLongPressTouchPoint { if configuration.preferences.javaScriptEnabled { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in if error != nil { print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")") } else if let value = value as? [String: Any?] { @@ -778,11 +834,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if #available(iOS 14.0, *) { let contentWorlds = configuration.userContentController.getContentWorlds(with: windowId) for contentWorld in contentWorlds { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source, contentWorld: contentWorld) } } else { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source) } } @@ -978,6 +1034,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } } + if newSettingsMap["isUserInteractionEnabled"] != nil && settings?.isUserInteractionEnabled != newSettings.isUserInteractionEnabled { + isUserInteractionEnabled = newSettings.isUserInteractionEnabled + } + + if newSettingsMap["alpha"] != nil, settings?.alpha != newSettings.alpha, let viewAlpha = newSettings.alpha { + alpha = CGFloat(viewAlpha) + } + if newSettingsMap["transparentBackground"] != nil && settings?.transparentBackground != newSettings.transparentBackground { if newSettings.transparentBackground { isOpaque = false @@ -1031,33 +1095,40 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if newSettingsMap["enableViewportScale"] != nil && settings?.enableViewportScale != newSettings.enableViewportScale { if !newSettings.enableViewportScale { - if configuration.userContentController.userScripts.contains(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) - evaluateJavaScript(NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(EnableViewportScaleJS.NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE()) } } else { - evaluateJavaScript(ENABLE_VIEWPORT_SCALE_JS_SOURCE) - configuration.userContentController.addUserScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) + evaluateJavaScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["supportZoom"] != nil && settings?.supportZoom != newSettings.supportZoom { if newSettings.supportZoom { - if configuration.userContentController.userScripts.contains(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - evaluateJavaScript(SUPPORT_ZOOM_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(SupportZoomJS.SUPPORT_ZOOM_JS_SOURCE()) } } else { - evaluateJavaScript(NOT_SUPPORT_ZOOM_JS_SOURCE) - configuration.userContentController.addUserScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) + evaluateJavaScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["useOnLoadResource"] != nil && settings?.useOnLoadResource != newSettings.useOnLoadResource { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE, - enable: newSettings.useOnLoadResource, - pluginScript: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: OnLoadResourceJS.FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE(), + enable: newSettings.useOnLoadResource, + pluginScript: OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useOnLoadResource = false } @@ -1065,28 +1136,58 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if newSettingsMap["useShouldInterceptAjaxRequest"] != nil && settings?.useShouldInterceptAjaxRequest != newSettings.useShouldInterceptAjaxRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptAjaxRequest, - pluginScript: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptAjaxRequest, + pluginScript: InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: newSettings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: newSettings.useOnAjaxProgress)) + } } else { newSettings.useShouldInterceptAjaxRequest = false } } + if newSettingsMap["useOnAjaxReadyStateChange"] != nil && settings?.useOnAjaxReadyStateChange != newSettings.useOnAjaxReadyStateChange { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(newSettings.useOnAjaxReadyStateChange);") + } + } else { + newSettings.useOnAjaxReadyStateChange = false + } + } + + if newSettingsMap["useOnAjaxProgress"] != nil && settings?.useOnAjaxProgress != newSettings.useOnAjaxProgress { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(newSettings.useOnAjaxProgress);") + } + } else { + newSettings.useOnAjaxProgress = false + } + } + if newSettingsMap["interceptOnlyAsyncAjaxRequests"] != nil && settings?.interceptOnlyAsyncAjaxRequests != newSettings.interceptOnlyAsyncAjaxRequests { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled, let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE, - enable: newSettings.interceptOnlyAsyncAjaxRequests, - pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE(), + enable: newSettings.interceptOnlyAsyncAjaxRequests, + pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + } } } if newSettingsMap["useShouldInterceptFetchRequest"] != nil && settings?.useShouldInterceptFetchRequest != newSettings.useShouldInterceptFetchRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptFetchRequest, - pluginScript: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptFetchRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptFetchRequest, + pluginScript: InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useShouldInterceptFetchRequest = false } @@ -1531,7 +1632,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let functionArgumentNames = functionArgumentNamesList.joined(separator: ", ") let functionArgumentValues = functionArgumentValuesList.joined(separator: ", ") - jsToInject = CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS + jsToInject = CallAsyncJavaScriptBelowIOS14WrapperJS.CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, with: functionArgumentNames) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, with: functionArgumentValues) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, with: Util.JSONStringify(value: arguments)) @@ -1562,15 +1663,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, scriptAttributes += " script.id = '\(scriptIdEscaped)'; " scriptAttributes += """ script.onload = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); } }; """ scriptAttributes += """ script.onerror = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); } }; """ @@ -1776,7 +1877,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil // cancel the download @@ -1794,7 +1895,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: response.suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil } @@ -1887,7 +1988,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: navigationResponse.response.expectedContentLength, suggestedFilename: navigationResponse.response.suggestedFilename, textEncodingName: navigationResponse.response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) if useOnNavigationResponse == nil || !useOnNavigationResponse! { decisionHandler(.cancel) } @@ -1922,7 +2023,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, initializeWindowIdJS() InAppWebView.credentialsProposed = [] - evaluateJavaScript(PLATFORM_READY_JS_SOURCE, completionHandler: nil) + evaluateJavaScript(JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE, completionHandler: nil) // sometimes scrollView.contentSize doesn't fit all the frame.size available // so, we call setNeedsLayout to redraw the layout @@ -2072,7 +2173,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if let scheme = challenge.protectionSpace.protocol, scheme == "https" { // workaround for ProtectionSpace SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { if let sslCertificate = challenge.protectionSpace.sslCertificate { DispatchQueue.main.async { InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate @@ -2092,7 +2193,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, break case 1: // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let exceptions = SecTrustCopyExceptions(serverTrust) SecTrustSetExceptions(serverTrust, exceptions) let credential = URLCredential(trust: serverTrust) @@ -2794,167 +2895,250 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard javaScriptBridgeEnabled else { + return + } + guard let body = message.body as? [String: Any?] else { return } - if ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"].contains(message.name) { - var messageLevel = 1 - switch (message.name) { - case "consoleLog": - messageLevel = 1 - break - case "consoleDebug": - // on Android, console.debug is TIP - messageLevel = 0 - break - case "consoleError": - messageLevel = 3 - break - case "consoleInfo": - // on Android, console.info is LOG - messageLevel = 1 - break - case "consoleWarn": - messageLevel = 2 - break - default: - messageLevel = 1 - break + guard let bridgeSecret = body["_bridgeSecret"] as? String, bridgeSecret == exceptedBridgeSecret else { + print("Bridge access attempt with wrong secret token, possibly from malicious code from origin \(message.frameInfo.securityOrigin)") + return + } + + var sourceOrigin: URL? = nil + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + let requestUrl = message.frameInfo.request.url + + var isOriginAllowed = false + if let javaScriptHandlersOriginAllowList = settings?.javaScriptHandlersOriginAllowList { + if let origin = sourceOrigin?.absoluteString { + for allowedOrigin in javaScriptHandlersOriginAllowList { + if origin.range(of: allowedOrigin, options: .regularExpression, range: nil, locale: nil) != nil { + isOriginAllowed = true + break + } + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true + } + + if !isOriginAllowed { + print("Bridge access attempt from an origin not allowed: \(message.frameInfo.securityOrigin)") + return + } + + if message.name == "callHandler" { + guard let handlerName = body["handlerName"] as? String else { + print("handlerName is null or undefined") + return } - let consoleMessage = body["message"] as? String ?? "" let _windowId = body["_windowId"] as? Int64 var webView = self if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } - webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) - } else if message.name == "callHandler", let handlerName = body["handlerName"] as? String { - if handlerName == "onPrintRequest" { - let settings = PrintJobSettings() - settings.handledByClient = true - if let printJobId = printCurrentPage(settings: settings) { - let callback = WebViewChannelDelegate.PrintRequestCallback() - callback.nonNullSuccess = { (handledByClient: Bool) in - return !handledByClient + var isInternalHandler = true + switch (handlerName) { + case "onPrintRequest": + let settings = PrintJobSettings() + settings.handledByClient = true + if let printJobId = webView.printCurrentPage(settings: settings) { + let callback = WebViewChannelDelegate.PrintRequestCallback() + callback.nonNullSuccess = { (handledByClient: Bool) in + return !handledByClient + } + callback.defaultBehaviour = { (handledByClient: Bool?) in + if let printJob = webView.plugin?.printJobManager?.jobs[printJobId] { + printJob?.disposeNoDismiss() + } + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + webView.channelDelegate?.onPrintRequest(url: webView.url, printJobId: printJobId, callback: callback) } - callback.defaultBehaviour = { [weak self] (handledByClient: Bool?) in - if let printJob = self?.plugin?.printJobManager?.jobs[printJobId] { - printJob?.disposeNoDismiss() + break + case "onConsoleMessage": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first { + var messageLevel = 1 + switch (jsonData["level"] as? String) { + case "log": + messageLevel = 1 + break + case "debug": + // on Android, console.debug is TIP + messageLevel = 0 + break + case "error": + messageLevel = 3 + break + case "info": + // on Android, console.info is LOG + messageLevel = 1 + break + case "warn": + messageLevel = 2 + break + default: + messageLevel = 1 + break + } + let consoleMessage = jsonData["message"] as? String ?? "" + + webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) } } - callback.error = { [weak callback] (code: String, message: String?, details: Any?) in - print(code + ", " + (message ?? "")) - callback?.defaultBehaviour(nil) + break + case "onFindResultReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let findResult = jsonData["findResult"] as? [String: Any], + let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, + let numberOfMatches = findResult["numberOfMatches"] as? Int, + let isDoneCounting = findResult["isDoneCounting"] as? Bool { + webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + } } - channelDelegate?.onPrintRequest(url: url, printJobId: printJobId, callback: callback) - } - return + break + case "onCallAsyncJavaScriptResultBelowIOS14Received": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let resultUuid = jsonData["resultUuid"] as? String, + let result = webView.callAsyncJavaScriptBelowIOS14Results[resultUuid] { + result([ + "value": jsonData["value"], + "error": jsonData["error"] + ]) + webView.callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid) + } + } + break + case "onWebMessagePortMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let webMessageChannelId = jsonData["webMessageChannelId"] as? String, + let index = jsonData["index"] as? Int64 { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { + webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) + } + } + } + break + case "onWebMessageListenerPostMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, let jsObjectName = jsonData["jsObjectName"] as? String { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageListener = webView.webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { + let isMainFrame = message.frameInfo.isMainFrame + + var scheme: String? = nil + var host: String? = nil + var port: Int? = nil + if #available(iOS 9.0, *) { + let sourceOrigin = message.frameInfo.securityOrigin + scheme = sourceOrigin.protocol + host = sourceOrigin.host + port = sourceOrigin.port + } else if let url = message.frameInfo.request.url { + scheme = url.scheme + host = url.host + port = url.port + } + + if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { + return + } + + var sourceOrigin: URL? = nil + if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") + } + webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + } + } + } + break + default: + isInternalHandler = false + break } let _callHandlerID = body["_callHandlerID"] as? Int64 ?? 0 - let args = body["args"] as? String ?? "" - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView + if isInternalHandler { + evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; +} +""", completionHandler: nil) + return } + let args = body["args"] as? String ?? "" + let callback = WebViewChannelDelegate.CallJsHandlerCallback() - callback.defaultBehaviour = { [weak self] (response: Any?) in + callback.defaultBehaviour = { (response: Any?) in var json = "null" if let r = response as? String { json = r } - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].resolve(\(json)); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(\(json)); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } - callback.error = { [weak self] (code: String, message: String?, details: Any?) in + callback.error = { (code: String, message: String?, details: Any?) in let errorMessage = code + (message != nil ? ", " + (message ?? "") : "") print(errorMessage) - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } if let channelDelegate = webView.channelDelegate { - channelDelegate.onCallJsHandler(handlerName: handlerName, args: args, callback: callback) - } - } else if message.name == "onFindResultReceived", - let findResult = body["findResult"] as? [String: Any], - let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, - let numberOfMatches = findResult["numberOfMatches"] as? Int, - let isDoneCounting = findResult["isDoneCounting"] as? Bool { - - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - } else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received", - let resultUuid = body["resultUuid"] as? String, - let result = callAsyncJavaScriptBelowIOS14Results[resultUuid] { - result([ - "value": body["value"], - "error": body["error"] - ]) - callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid) - } else if message.name == "onWebMessagePortMessageReceived", - let webMessageChannelId = body["webMessageChannelId"] as? String, - let index = body["index"] as? Int64 { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageChannel = webMessageChannels[webMessageChannelId] { - webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) - } - } else if message.name == "onWebMessageListenerPostMessageReceived", let jsObjectName = body["jsObjectName"] as? String { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { - let isMainFrame = message.frameInfo.isMainFrame - - var scheme: String? = nil - var host: String? = nil - var port: Int? = nil - if #available(iOS 9.0, *) { - let sourceOrigin = message.frameInfo.securityOrigin - scheme = sourceOrigin.protocol - host = sourceOrigin.host - port = sourceOrigin.port - } else if let url = message.frameInfo.request.url { - scheme = url.scheme - host = url.host - port = url.port - } - - if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { - return - } - - var sourceOrigin: URL? = nil - if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { - sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") - } - webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + let data = JavaScriptHandlerFunctionData( + args: args, isMainFrame: message.frameInfo.isMainFrame, + origin: sourceOrigin?.absoluteString ?? "", + requestUrl: requestUrl?.absoluteString ?? "" + ) + channelDelegate.onCallJsHandler(handlerName: handlerName, data: data, callback: callback) } } } @@ -3094,7 +3278,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { public func getHitTestResult(completionHandler: @escaping (HitTestResult) -> Void) { if configuration.preferences.javaScriptEnabled, let lastTouchLocation = lastTouchPoint { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastTouchLocation.x),\(lastTouchLocation.y))", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint(\(lastTouchLocation.x),\(lastTouchLocation.y))", completionHandler: {(value, error) in if error != nil { print("getHitTestResult error: \(error?.localizedDescription ?? "")") completionHandler(HitTestResult(type: .unknownType, extra: nil)) @@ -3114,7 +3298,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if configuration.preferences.javaScriptEnabled { // add some delay to make it sure _lastAnchorOrImageTouched is updated DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched", completionHandler: {(value, error) in let lastAnchorOrImageTouched = value as? [String: Any?] completionHandler(lastAnchorOrImageTouched, error) }) @@ -3128,7 +3312,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if configuration.preferences.javaScriptEnabled { // add some delay to make it sure _lastImageTouched is updated DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched", completionHandler: {(value, error) in + self.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched", completionHandler: {(value, error) in let lastImageTouched = value as? [String: Any?] completionHandler(lastImageTouched, error) }) @@ -3138,8 +3322,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } - public func clearFocus() { - self.scrollView.subviews.first?.resignFirstResponder() + public func clearFocus() -> Bool { + return self.scrollView.subviews.first?.resignFirstResponder() ?? false + } + + public func requestFocus() -> Bool { + return self.scrollView.subviews.first?.becomeFirstResponder() ?? false } public func getCertificate() -> SslCertificate? { @@ -3220,7 +3408,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } @@ -3255,6 +3443,32 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { public override var inputAccessoryView: UIView? { return settings?.disableInputAccessoryView ?? false ? nil : super.inputAccessoryView } + + private var _inputMethodEnabled = true + public override var inputView: UIView? { + return _inputMethodEnabled ? super.inputView : UIView() + } + + public func setInputMethodEnabled(enabled: Bool) { + _inputMethodEnabled = enabled + for subview in self.scrollView.subviews { + subview.reloadInputViews() + } + } + + public func hideInputMethod() { + endEditing(true) + } + + @available(iOS 15.0, *) + public func saveState() -> Data? { + return interactionState is NSData || interactionState is Data ? interactionState as? Data : nil + } + + @available(iOS 15.0, *) + public func restoreState(state: Data) { + interactionState = state + } public func runWindowBeforeCreatedCallbacks() { let callbacks = windowBeforeCreatedCallbacks @@ -3292,9 +3506,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { interceptOnlyAsyncAjaxRequestsPluginScript = nil if windowId == nil { configuration.userContentController.removeAllPluginScriptMessageHandlers() - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") configuration.userContentController.removeAllUserScripts() if #available(iOS 11.0, *) { configuration.userContentController.removeAllContentRuleLists() diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift index df5c3b59e..e3397d178 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewManager.swift @@ -19,7 +19,7 @@ public class InAppWebViewManager: ChannelDelegate { var windowAutoincrementId: Int64 = 0 init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -50,6 +50,14 @@ public class InAppWebViewManager: ChannelDelegate { clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: { result(true) }) + case "setJavaScriptBridgeName": + let bridgeName = arguments!["bridgeName"] as! String + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME(bridgeName: bridgeName) + result(true) + break + case "getJavaScriptBridgeName": + result(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) + break default: result(FlutterMethodNotImplemented) break diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift index 52ede006b..dc65eb0fc 100755 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/InAppWebViewSettings.swift @@ -27,6 +27,8 @@ public class InAppWebViewSettings: ISettings { var contentBlockers: [[String: [String : Any]]] = [] var minimumFontSize = 0 var useShouldInterceptAjaxRequest = false + var useOnAjaxReadyStateChange = false + var useOnAjaxProgress = false var interceptOnlyAsyncAjaxRequests = true var useShouldInterceptFetchRequest = false var incognito = false @@ -82,6 +84,14 @@ public class InAppWebViewSettings: ISettings { var maximumViewportInset: UIEdgeInsets? = nil var isInspectable = false var shouldPrintBackgrounds = false + var javaScriptHandlersOriginAllowList: [String]? = nil + var javaScriptBridgeEnabled = true + var javaScriptBridgeOriginAllowList: [String]? = nil + var javaScriptBridgeForMainFrameOnly = false + var pluginScriptsOriginAllowList: [String]? = nil + var pluginScriptsForMainFrameOnly = false + var isUserInteractionEnabled = true + var alpha: Double? = nil override init(){ super.init() @@ -97,6 +107,12 @@ public class InAppWebViewSettings: ISettings { maximumViewportInset = UIEdgeInsets.fromMap(map: maximumViewportInsetMap) settings.removeValue(forKey: "maximumViewportInset") } + // nullable values with primitive type (Int, Double, etc.) + // must be handled here as super.parse will not work + if let alphaValue = settings["alpha"] as? Double { + alpha = alphaValue + settings.removeValue(forKey: "alpha") + } let _ = super.parse(settings: settings) if #available(iOS 13.0, *) {} else { applePayAPIEnabled = false @@ -107,6 +123,8 @@ public class InAppWebViewSettings: ISettings { override func getRealSettings(obj: InAppWebView?) -> [String: Any?] { var realSettings: [String: Any?] = toMap() if let webView = obj { + realSettings["isUserInteractionEnabled"] = webView.isUserInteractionEnabled + realSettings["alpha"] = Double(webView.alpha) let configuration = webView.configuration if #available(iOS 9.0, *) { realSettings["userAgent"] = webView.customUserAgent diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index 8e7b3784d..354a62456 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -19,11 +19,9 @@ public class WebMessageChannel: FlutterMethodCallDelegate { self.id = id self.plugin = plugin super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.ports = [ WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self), WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self) @@ -35,7 +33,7 @@ public class WebMessageChannel: FlutterMethodCallDelegate { if let webView = self.webView { webView.evaluateJavascript(source: """ (function() { - \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"] = new MessageChannel(); })(); """) { (_) in completionHandler?(self) @@ -60,11 +58,11 @@ public class WebMessageChannel: FlutterMethodCallDelegate { ports.removeAll() webView?.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; if (webMessageChannel != null) { webMessageChannel.port1.close(); webMessageChannel.port2.close(); - delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + delete \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; } })(); """) diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift index e902aedb4..5b42e9bf2 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -23,11 +23,9 @@ public class WebMessageListener: FlutterMethodCallDelegate { self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) } public func assertOriginRulesValid() throws { @@ -96,23 +94,30 @@ public class WebMessageListener: FlutterMethodCallDelegate { }.joined(separator: ", ") let source = """ (function() { + \(WebMessageListener.isOriginAllowedJs) + var allowedOriginRules = [\(allowedOriginRulesString)]; var isPageBlank = window.location.href === "about:blank"; var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null; var host = !isPageBlank ? window.location.hostname : null; var port = !isPageBlank ? window.location.port : null; - if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) { + if (_isOriginAllowed(allowedOriginRules, scheme, host, port)) { window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)'); } })(); """ + + let allowedOriginRules = webView.settings?.pluginScriptsOriginAllowList + let forMainFrameOnly = webView.settings?.pluginScriptsForMainFrameOnly ?? true + webView.configuration.userContentController.addPluginScript(PluginScript( groupName: "WebMessageListener-" + id + "-" + jsObjectName, source: source, injectionTime: .atDocumentStart, - forMainFrameOnly: false, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, requiredInAllContentWorlds: false, - messageHandlerNames: ["onWebMessageListenerPostMessageReceived"] + messageHandlerNames: [] )) webView.configuration.userContentController.sync(scriptMessageHandler: webView) } @@ -177,6 +182,86 @@ public class WebMessageListener: FlutterMethodCallDelegate { } return false } + + private static let isOriginAllowedJs = """ + var _normalizeIPv6 = function(ip_string) { + // replace ipv4 address if any + var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); + if (ipv4) { + ip_string = ipv4[1]; + ipv4 = ipv4[2].match(/[0-9]+/g); + for (var i = 0;i < 4;i ++) { + var byte = parseInt(ipv4[i],10); + ipv4[i] = ("0" + byte.toString(16)).substr(-2); + } + ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; + } + + // take care of leading and trailing :: + ip_string = ip_string.replace(/^:|:$/g, ''); + + var ipv6 = ip_string.split(':'); + + for (var i = 0; i < ipv6.length; i ++) { + var hex = ipv6[i]; + if (hex != "") { + // normalize leading zeros + ipv6[i] = ("0000" + hex).substr(-4); + } + else { + // normalize grouped zeros :: + hex = []; + for (var j = ipv6.length; j <= 8; j ++) { + hex.push('0000'); + } + ipv6[i] = hex.join(':'); + } + } + + return ipv6.join(':'); + }; + + var _isOriginAllowed = function(allowedOriginRules, scheme, host, port) { + for (var rule of allowedOriginRules) { + if (rule === "*") { + return true; + } + if (scheme == null || scheme === "") { + continue; + } + if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { + continue; + } + var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; + var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; + var IPv6 = null; + if (rule.host != null && rule.host[0] === "[") { + try { + IPv6 = _normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); + } catch {} + } + var hostIPv6 = null; + try { + hostIPv6 = _normalizeIPv6(host); + } catch {} + + var schemeAllowed = scheme == rule.scheme; + + var hostAllowed = rule.host == null || + rule.host === "" || + host === rule.host || + (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || + (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); + + var portAllowed = rulePort === currentPort; + + if (schemeAllowed && hostAllowed && portAllowed) { + return true; + } + } + return false; + }; + """ public func dispose() { channelDelegate?.dispose() diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift index 46bab6d05..91aa7589a 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -345,8 +345,10 @@ public class WebViewChannelDelegate: ChannelDelegate { } break case .clearFocus: - webView?.clearFocus() - result(true) + result(webView?.clearFocus()) + break + case .requestFocus: + result(webView?.requestFocus()) break case .setContextMenu: if let webView = webView { @@ -675,6 +677,36 @@ public class WebViewChannelDelegate: ChannelDelegate { } else { result(false) } + break + case .setInputMethodEnabled: + if let webView = webView { + let enabled = arguments!["enabled"] as! Bool + webView.setInputMethodEnabled(enabled: enabled) + } + result(true) + break + case .hideInputMethod: + if let webView = webView { + webView.hideInputMethod() + } + result(true) + break + case .saveState: + if let webView = webView, #available(iOS 15.0, *) { + result(webView.saveState()) + } else { + result(nil) + } + break + case .restoreState: + if let webView = webView, #available(iOS 15.0, *) { + let state = arguments!["state"] as! FlutterStandardTypedData + webView.restoreState(state: state.data) + result(true) + } else { + result(false) + } + break } } @@ -705,8 +737,8 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onContentSizeChanged", arguments: arguments) } - public func onDownloadStartRequest(request: DownloadStartRequest) { - channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) + public func onDownloadStarting(request: DownloadStartRequest) { + channel?.invokeMethod("onDownloadStarting", arguments: request.toMap()) } public func onCreateContextMenu(hitTestResult: HitTestResult) { @@ -965,7 +997,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -997,7 +1029,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1029,7 +1061,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1083,14 +1115,14 @@ public class WebViewChannelDelegate: ChannelDelegate { } } - public func onCallJsHandler(handlerName: String, args: String, callback: CallJsHandlerCallback) { + public func onCallJsHandler(handlerName: String, data: JavaScriptHandlerFunctionData, callback: CallJsHandlerCallback) { if channel == nil { callback.defaultBehaviour(nil) return } let arguments: [String: Any?] = [ "handlerName": handlerName, - "args": args + "data": data.toMap() ] channel?.invokeMethod("onCallJsHandler", arguments: arguments, callback: callback) } @@ -1142,7 +1174,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { diff --git a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift index 899cd769b..5f01e2273 100644 --- a/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift +++ b/flutter_inappwebview_ios/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift @@ -59,6 +59,7 @@ public enum WebViewChannelDelegateMethods: String { case getSelectedText = "getSelectedText" case getHitTestResult = "getHitTestResult" case clearFocus = "clearFocus" + case requestFocus = "requestFocus" case setContextMenu = "setContextMenu" case requestFocusNodeHref = "requestFocusNodeHref" case requestImageRef = "requestImageRef" @@ -90,4 +91,8 @@ public enum WebViewChannelDelegateMethods: String { case getMicrophoneCaptureState = "getMicrophoneCaptureState" case setMicrophoneCaptureState = "setMicrophoneCaptureState" case loadSimulatedRequest = "loadSimulatedRequest" + case setInputMethodEnabled = "setInputMethodEnabled" + case hideInputMethod = "hideInputMethod" + case saveState = "saveState" + case restoreState = "restoreState" } diff --git a/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift b/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift index 235d8f9f5..49424d0ad 100755 --- a/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/MyCookieManager.swift @@ -16,7 +16,7 @@ public class MyCookieManager: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift b/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift index a375fdfb0..e86b442c4 100755 --- a/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/MyWebStorageManager.swift @@ -16,7 +16,7 @@ public class MyWebStorageManager: ChannelDelegate { private var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift b/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift index a49d5316d..ae5902638 100644 --- a/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift +++ b/flutter_inappwebview_ios/ios/Classes/PlatformUtil.swift @@ -12,7 +12,7 @@ public class PlatformUtil: ChannelDelegate { var plugin: SwiftFlutterPlugin? init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } @@ -50,6 +50,7 @@ public class PlatformUtil: ChannelDelegate { static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String { let formatter = DateFormatter() + formatter.locale = locale formatter.dateFormat = format formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift index 5622691a1..f1f83db00 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift @@ -7,15 +7,28 @@ import Foundation -let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """ -(function(obj) { - (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { - \(PluginScriptsUtil.VAR_FUNCTION_BODY) - })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }).catch(function(error) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }); - return null; -})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); -""" +public class CallAsyncJavaScriptBelowIOS14WrapperJS { + + public static func CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() -> String { + return """ + (function(obj) { + (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { + \(PluginScriptsUtil.VAR_FUNCTION_BODY) + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': value, + 'error': null, + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }).catch(function(error) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': null, + 'error': error + '', + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }); + return null; + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift index d244d8993..5636ea62c 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/ConsoleLogJS.swift @@ -7,46 +7,59 @@ import Foundation -let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" - -let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript( - groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: CONSOLE_LOG_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"]) - -// the message needs to be concatenated with '' in order to have the same behavior like on Android -let CONSOLE_LOG_JS_SOURCE = """ -(function(console) { - - function _callHandler(oldLog, args) { - var message = ''; - for (var i in args) { - try { - message += message === '' ? args[i] : ' ' + args[i]; - } catch(ignored) {} - } - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId}); +public class ConsoleLogJS { + + public static let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame. + // Using it also on non-main frames could cause issues + // such as https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738 + public static func CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: CONSOLE_LOG_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) } - - var oldLogs = { - 'consoleLog': console.log, - 'consoleDebug': console.debug, - 'consoleError': console.error, - 'consoleInfo': console.info, - 'consoleWarn': console.warn - }; - - for (var k in oldLogs) { - (function(oldLog) { - console[oldLog.replace('console', '').toLowerCase()] = function() { - oldLogs[oldLog].apply(null, arguments); - _callHandler(oldLog, arguments); + + // the message needs to be concatenated with '' in order to have the same behavior like on Android + public static func CONSOLE_LOG_JS_SOURCE() -> String { + return """ + (function(console) { + + function _callHandler(logLevel, args) { + var message = ''; + for (var i in args) { + try { + message += message === '' ? args[i] : ' ' + args[i]; + } catch(_) {} + } + try { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onConsoleMessage', {'level': logLevel, 'message': message}) + } catch(_) {} + } + + var oldLogs = { + 'consoleLog': console.log, + 'consoleDebug': console.debug, + 'consoleError': console.error, + 'consoleInfo': console.info, + 'consoleWarn': console.warn + }; + + for (var k in oldLogs) { + (function(oldLog) { + var logLevel = oldLog.replace('console', '').toLowerCase(); + console[logLevel] = function() { + oldLogs[oldLog].apply(null, arguments); + _callHandler(logLevel, arguments); + } + })(k); } - })(k); + })(window.console); + """ } -})(window.console); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift index 593ba00eb..efa07583a 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/EnableViewportScaleJS.swift @@ -7,30 +7,39 @@ import Foundation -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" - -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', 'width=device-width'); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" - -let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" +public class EnableViewportScaleJS { + + public static let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift index b3c22282e..26220f8a5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindElementsAtPointJS.swift @@ -7,67 +7,75 @@ import Foundation -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" - -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_ELEMENTS_AT_POINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -/** - https://developer.android.com/reference/android/webkit/WebView.HitTestResult - */ -let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) { - var hitTestResultType = { - UNKNOWN_TYPE: 0, - PHONE_TYPE: 2, - GEO_TYPE: 3, - EMAIL_TYPE: 4, - IMAGE_TYPE: 5, - SRC_ANCHOR_TYPE: 7, - SRC_IMAGE_ANCHOR_TYPE: 8, - EDIT_TEXT_TYPE: 9 - }; - var element = document.elementFromPoint(x, y); - var data = { - type: 0, - extra: null - }; - while (element) { - if (element.tagName === 'IMG' && element.src) { - if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { - data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; - } else { - data.type = hitTestResultType.IMAGE_TYPE; - } - data.extra = element.src; - break; - } else if (element.tagName === 'A' && element.href) { - if (element.href.indexOf('mailto:') === 0) { - data.type = hitTestResultType.EMAIL_TYPE; - data.extra = element.href.replace('mailto:', ''); - } else if (element.href.indexOf('tel:') === 0) { - data.type = hitTestResultType.PHONE_TYPE; - data.extra = element.href.replace('tel:', ''); - } else if (element.href.indexOf('geo:') === 0) { - data.type = hitTestResultType.GEO_TYPE; - data.extra = element.href.replace('geo:', ''); - } else { - data.type = hitTestResultType.SRC_ANCHOR_TYPE; - data.extra = element.href; +public class FindElementsAtPointJS { + public static let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_ELEMENTS_AT_POINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + /** + https://developer.android.com/reference/android/webkit/WebView.HitTestResult + */ + public static func FIND_ELEMENTS_AT_POINT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint = function(x, y) { + var hitTestResultType = { + UNKNOWN_TYPE: 0, + PHONE_TYPE: 2, + GEO_TYPE: 3, + EMAIL_TYPE: 4, + IMAGE_TYPE: 5, + SRC_ANCHOR_TYPE: 7, + SRC_IMAGE_ANCHOR_TYPE: 8, + EDIT_TEXT_TYPE: 9 + }; + var element = document.elementFromPoint(x, y); + var data = { + type: 0, + extra: null + }; + while (element) { + if (element.tagName === 'IMG' && element.src) { + if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { + data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; + } else { + data.type = hitTestResultType.IMAGE_TYPE; + } + data.extra = element.src; + break; + } else if (element.tagName === 'A' && element.href) { + if (element.href.indexOf('mailto:') === 0) { + data.type = hitTestResultType.EMAIL_TYPE; + data.extra = element.href.replace('mailto:', ''); + } else if (element.href.indexOf('tel:') === 0) { + data.type = hitTestResultType.PHONE_TYPE; + data.extra = element.href.replace('tel:', ''); + } else if (element.href.indexOf('geo:') === 0) { + data.type = hitTestResultType.GEO_TYPE; + data.extra = element.href.replace('geo:', ''); + } else { + data.type = hitTestResultType.SRC_ANCHOR_TYPE; + data.extra = element.href; + } + break; + } else if ( + (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || + element.tagName === 'TEXTAREA') { + data.type = hitTestResultType.EDIT_TEXT_TYPE + } + element = element.parentNode; } - break; - } else if ( - (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || - element.tagName === 'TEXTAREA') { - data.type = hitTestResultType.EDIT_TEXT_TYPE + return data; } - element = element.parentNode; + """ } - return data; } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift index f0db61452..5006f04b7 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/FindTextHighlightJS.swift @@ -7,181 +7,180 @@ import Foundation -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" -let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount" -let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight" -let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting" - -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_TEXT_HIGHLIGHT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onFindResultReceived"]) - -let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """ -\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) { - if (element) { - if (element.nodeType == 3) { - // Text node - - var elementTmp = element; - while (true) { - var value = elementTmp.nodeValue; // Search for keyword in text node - var idx = value.toLowerCase().indexOf(keyword); - - if (idx < 0) break; - - var span = document.createElement("span"); - var text = document.createTextNode(value.substr(idx, keyword.length)); - span.appendChild(text); - - span.setAttribute( - "id", - "\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ); - span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00"; - span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); - - text = document.createTextNode(value.substr(idx + keyword.length)); - element.deleteData(idx, value.length - idx); - - var next = element.nextSibling; - element.parentNode.insertBefore(span, next); - element.parentNode.insertBefore(text, next); - element = text; - - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++; - elementTmp = document.createTextNode( - value.substr(idx + keyword.length) - ); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId +public class FindTextHighlightJS { + public static let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" + public static func FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._searchResultCount" + } + public static func FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._currentHighlight" + } + public static func FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._isDoneCounting" + } + + // This plugin is only for main frame + public static func FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_TEXT_HIGHLIGHT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func FIND_TEXT_HIGHLIGHT_JS_SOURCE() -> String { + return """ + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement = function(element, keyword) { + if (element) { + if (element.nodeType == 3) { + // Text node + + var elementTmp = element; + while (true) { + var value = elementTmp.nodeValue; // Search for keyword in text node + var idx = value.toLowerCase().indexOf(keyword); + + if (idx < 0) break; + + var span = document.createElement("span"); + var text = document.createTextNode(value.substr(idx, keyword.length)); + span.appendChild(text); + + span.setAttribute( + "id", + "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ); + span.setAttribute("class", "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) == 0 ? "#FF9732" : "#FFFF00"; + span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); + + text = document.createTextNode(value.substr(idx + keyword.length)); + element.deleteData(idx, value.length - idx); + + var next = element.nextSibling; + element.parentNode.insertBefore(span, next); + element.parentNode.insertBefore(text, next); + element = text; + + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE())++; + elementTmp = document.createTextNode( + value.substr(idx + keyword.length) + ); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + } else if (element.nodeType == 1) { + // Element node + if ( + element.style.display != "none" && + element.nodeName.toLowerCase() != "select" + ) { + for (var i = element.childNodes.length - 1; i >= 0; i--) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement( + element.childNodes[element.childNodes.length - 1 - i], + keyword + ); + } + } } - ); - } - } else if (element.nodeType == 1) { - // Element node - if ( - element.style.display != "none" && - element.nodeName.toLowerCase() != "select" - ) { - for (var i = element.childNodes.length - 1; i >= 0; i--) { - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement( - element.childNodes[element.childNodes.length - 1 - i], - keyword - ); + } } - } - } - } -} - -// the main entry point to start the search -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) { - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches(); - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase()); - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true; - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId - } - ); -} - -// helper function, recursively removes the highlights in elements and their children -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) { - if (element) { - if (element.nodeType == 1) { - if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") { - var text = element.removeChild(element.firstChild); - element.parentNode.insertBefore(text, element); - element.parentNode.removeChild(element); - return true; - } else { - var normalize = false; - for (var i = element.childNodes.length - 1; i >= 0; i--) { - if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) { - normalize = true; + + // the main entry point to start the search + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync = function(keyword) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches(); + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement(document.body, keyword.toLowerCase()); + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = true; + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + + // helper function, recursively removes the highlights in elements and their children + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement = function(element) { + if (element) { + if (element.nodeType == 1) { + if (element.getAttribute("class") == "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight") { + var text = element.removeChild(element.firstChild); + element.parentNode.insertBefore(text, element); + element.parentNode.removeChild(element); + return true; + } else { + var normalize = false; + for (var i = element.childNodes.length - 1; i >= 0; i--) { + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(element.childNodes[i])) { + normalize = true; + } + } + if (normalize) { + element.normalize(); + } + } + } } + return false; } - if (normalize) { - element.normalize(); + + // the main entry point to remove the highlights + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches = function() { + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(document.body); } - } - } - } - return false; -} - -// the main entry point to remove the highlights -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() { - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) { - if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return; - - var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1); - idx = - idx < 0 - ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1 - : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ? 0 - : idx; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx; - - var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx); - if (scrollTo) { - var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - for (var i = 0; i < highlights.length; i++) { - var span = highlights[i]; - span.style.backgroundColor = "#FFFF00"; - } - scrollTo.style.backgroundColor = "#FF9732"; - - scrollTo.scrollIntoView({ - behavior: "auto", - block: "center" - }); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext = function(forward) { + if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) <= 0) return; + + var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) + (forward ? +1 : -1); + idx = + idx < 0 + ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) - 1 + : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ? 0 + : idx; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = idx; + + var scrollTo = document.getElementById("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + idx); + if (scrollTo) { + var highlights = document.getElementsByClassName("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + for (var i = 0; i < highlights.length; i++) { + var span = highlights[i]; + span.style.backgroundColor = "#FFFF00"; + } + scrollTo.style.backgroundColor = "#FF9732"; + + scrollTo.scrollIntoView({ + behavior: "auto", + block: "center" + }); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } } - ); - } + """ + } } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift index 28ebb58b3..e8ca40c76 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift @@ -7,261 +7,288 @@ import Foundation -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest" - -let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests"; - -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_AJAX_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript { - return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);", - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: [] - ); -} - -let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true; -(function(ajax) { - var send = ajax.prototype.send; - var open = ajax.prototype.open; - var setRequestHeader = ajax.prototype.setRequestHeader; - ajax.prototype._flutter_inappwebview_url = null; - ajax.prototype._flutter_inappwebview_method = null; - ajax.prototype._flutter_inappwebview_isAsync = null; - ajax.prototype._flutter_inappwebview_user = null; - ajax.prototype._flutter_inappwebview_password = null; - ajax.prototype._flutter_inappwebview_password = null; - ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false; - ajax.prototype._flutter_inappwebview_request_headers = {}; - function convertRequestResponse(request, callback) { - if (request.response != null && request.responseType != null) { - switch (request.responseType) { - case 'arraybuffer': - callback(new Uint8Array(request.response)); - return; - case 'blob': - const reader = new FileReader(); - reader.addEventListener('loadend', function() { - callback(new Uint8Array(reader.result)); - }); - reader.readAsArrayBuffer(blob); - return; - case 'document': - callback(request.response.documentElement.outerHTML); - return; - case 'json': - callback(request.response); - return; - }; +public class InterceptAjaxRequestJS { + + public static let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptAjaxRequest" } - callback(null); - }; - ajax.prototype.open = function(method, url, isAsync, user, password) { - isAsync = (isAsync != null) ? isAsync : true; - this._flutter_inappwebview_url = url; - this._flutter_inappwebview_method = method; - this._flutter_inappwebview_isAsync = isAsync; - this._flutter_inappwebview_user = user; - this._flutter_inappwebview_password = password; - this._flutter_inappwebview_request_headers = {}; - open.call(this, method, url, isAsync, user, password); - }; - ajax.prototype.setRequestHeader = function(header, value) { - this._flutter_inappwebview_request_headers[header] = value; - setRequestHeader.call(this, header, value); - }; - function handleEvent(e) { - var self = this; - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) { - var headers = this.getAllResponseHeaders(); - var responseHeaders = {}; - if (headers != null) { - var arr = headers.trim().split(/[\\r\\n]+/); - arr.forEach(function (line) { - var parts = line.split(': '); - var header = parts.shift(); - var value = parts.join(': '); - responseHeaders[header] = value; - }); - } - convertRequestResponse(this, function(response) { - var ajaxRequest = { - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - readyState: self.readyState, - status: self.status, - responseURL: self.responseURL, - responseType: self.responseType, - response: response, - responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, - responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, - statusText: self.statusText, - responseHeaders, responseHeaders, - event: { - type: e.type, - loaded: e.loaded, - lengthComputable: e.lengthComputable, - total: e.total - } - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - }); - }); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxReadyStateChange" } - }; - ajax.prototype.send = function(data) { - var self = this; - var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false; - if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) { - if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) { - this._flutter_inappwebview_already_onreadystatechange_wrapped = true; - var onreadystatechange = this.onreadystatechange; - this.onreadystatechange = function() { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) { - var headers = this.getAllResponseHeaders(); - var responseHeaders = {}; - if (headers != null) { - var arr = headers.trim().split(/[\\r\\n]+/); - arr.forEach(function (line) { - var parts = line.split(': '); - var header = parts.shift(); - var value = parts.join(': '); - responseHeaders[header] = value; - }); - } - convertRequestResponse(this, function(response) { - var ajaxRequest = { - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - readyState: self.readyState, - status: self.status, - responseURL: self.responseURL, - responseType: self.responseType, - response: response, - responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, - responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, - statusText: self.statusText, - responseHeaders: responseHeaders - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - if (onreadystatechange != null) { - onreadystatechange(); - } - }); - }); - } else if (onreadystatechange != null) { - onreadystatechange(); - } - }; - } - this.addEventListener('loadstart', handleEvent); - this.addEventListener('load', handleEvent); - this.addEventListener('loadend', handleEvent); - this.addEventListener('progress', handleEvent); - this.addEventListener('error', handleEvent); - this.addEventListener('abort', handleEvent); - this.addEventListener('timeout', handleEvent); - \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) { - var ajaxRequest = { - data: data, - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - responseType: self.responseType - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxProgress" + } + + public static func FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._interceptOnlyAsyncAjaxRequests" + } + + public static func INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool, initialUseOnAjaxReadyStateChange: Bool = false, initialUseOnAjaxProgress: Bool = false) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress: initialUseOnAjaxProgress), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) = \(onlyAsync);", + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: [] + ); + } + + public static func INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: Bool, initialUseOnAjaxProgress: Bool) -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) = true; + \(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(initialUseOnAjaxReadyStateChange); + \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(initialUseOnAjaxProgress); + (function(ajax) { + var send = ajax.prototype.send; + var open = ajax.prototype.open; + var setRequestHeader = ajax.prototype.setRequestHeader; + ajax.prototype._flutter_inappwebview_url = null; + ajax.prototype._flutter_inappwebview_method = null; + ajax.prototype._flutter_inappwebview_isAsync = null; + ajax.prototype._flutter_inappwebview_user = null; + ajax.prototype._flutter_inappwebview_password = null; + ajax.prototype._flutter_inappwebview_password = null; + ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false; + ajax.prototype._flutter_inappwebview_request_headers = {}; + function convertRequestResponse(request, callback) { + if (request.response != null && request.responseType != null) { + switch (request.responseType) { + case 'arraybuffer': + callback(new Uint8Array(request.response)); + return; + case 'blob': + const reader = new FileReader(); + reader.addEventListener('loadend', function() { + callback(new Uint8Array(reader.result)); + }); + reader.readAsArrayBuffer(blob); + return; + case 'document': + callback(request.response.documentElement.outerHTML); + return; + case 'json': + callback(request.response); return; }; - if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); - if (result.headers != null) { - result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; - } else { - result.headers = { 'Content-Type': formDataContentType }; - } - } - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) { - data = result.data; - } else if (result.data.length > 0) { - data = new Uint8Array(result.data); + } + callback(null); + }; + ajax.prototype.open = function(method, url, isAsync, user, password) { + isAsync = (isAsync != null) ? isAsync : true; + this._flutter_inappwebview_url = url; + this._flutter_inappwebview_method = method; + this._flutter_inappwebview_isAsync = isAsync; + this._flutter_inappwebview_user = user; + this._flutter_inappwebview_password = password; + this._flutter_inappwebview_request_headers = {}; + open.call(this, method, url, isAsync, user, password); + }; + ajax.prototype.setRequestHeader = function(header, value) { + this._flutter_inappwebview_request_headers[header] = value; + setRequestHeader.call(this, header, value); + }; + function handleEvent(e) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) === false || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) == null || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) === false) { + return; + } + var self = this; + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\\r\\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); } - self.withCredentials = result.withCredentials; - if (result.responseType != null && self._flutter_inappwebview_isAsync) { - self.responseType = result.responseType; - }; - if (result.headers != null) { - for (var header in result.headers) { - var value = result.headers[header]; - var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; - if (flutter_inappwebview_value == null) { - self._flutter_inappwebview_request_headers[header] = value; - } else { - self._flutter_inappwebview_request_headers[header] += ', ' + value; + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders, responseHeaders, + event: { + type: e.type, + loaded: e.loaded, + lengthComputable: e.lengthComputable, + total: e.total + } + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + }); + }); + } + }; + ajax.prototype.send = function(data) { + var self = this; + var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) === false; + if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true)) { + if (\(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) { + this._flutter_inappwebview_already_onreadystatechange_wrapped = true; + var realOnreadystatechange = this.onreadystatechange; + this.onreadystatechange = function() { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\\r\\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); + } + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders: responseHeaders + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }); + }); + } else if (realOnreadystatechange != null) { + realOnreadystatechange(); } - setRequestHeader.call(self, header, value); }; } - if ((self._flutter_inappwebview_method != result.method && result.method != null) || - (self._flutter_inappwebview_url != result.url && result.url != null) || - (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) || - (self._flutter_inappwebview_user != result.user && result.user != null) || - (self._flutter_inappwebview_password != result.password && result.password != null)) { - self.abort(); - self.open(result.method, result.url, result.isAsync, result.user, result.password); - } + this.addEventListener('loadstart', handleEvent); + this.addEventListener('load', handleEvent); + this.addEventListener('loadend', handleEvent); + this.addEventListener('progress', handleEvent); + this.addEventListener('error', handleEvent); + this.addEventListener('abort', handleEvent); + this.addEventListener('timeout', handleEvent); + \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(data).then(function(data) { + var ajaxRequest = { + data: data, + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + responseType: self.responseType + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + if (result.data != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) && result.data.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.data); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) || result.data == null) { + data = result.data; + } else if (result.data.length > 0) { + data = new Uint8Array(result.data); + } + self.withCredentials = result.withCredentials; + if (result.responseType != null && self._flutter_inappwebview_isAsync) { + self.responseType = result.responseType; + }; + if (result.headers != null) { + for (var header in result.headers) { + var value = result.headers[header]; + var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; + if (flutter_inappwebview_value == null) { + self._flutter_inappwebview_request_headers[header] = value; + } else { + self._flutter_inappwebview_request_headers[header] += ', ' + value; + } + setRequestHeader.call(self, header, value); + }; + } + if ((self._flutter_inappwebview_method != result.method && result.method != null) || + (self._flutter_inappwebview_url != result.url && result.url != null) || + (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) || + (self._flutter_inappwebview_user != result.user && result.user != null) || + (self._flutter_inappwebview_password != result.password && result.password != null)) { + self.abort(); + self.open(result.method, result.url, result.isAsync, result.user, result.password); + } + } + send.call(self, data); + }); + }); + } else { + send.call(this, data); } - send.call(self, data); - }); - }); - } else { - send.call(this, data); + }; + })(window.XMLHttpRequest); + """ } - }; -})(window.XMLHttpRequest); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift index 14539811c..953fa3aa8 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift @@ -7,147 +7,157 @@ import Foundation -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest" - -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_FETCH_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true; -(function(fetch) { - if (fetch == null) { - return; - } - window.fetch = async function(resource, init) { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == true) { - var fetchRequest = { - url: null, - method: null, - headers: null, - body: null, - mode: null, - credentials: null, - cache: null, - redirect: null, - referrer: null, - referrerPolicy: null, - integrity: null, - keepalive: null - }; - if (resource instanceof Request) { - fetchRequest.url = resource.url; - fetchRequest.method = resource.method; - fetchRequest.headers = resource.headers; - fetchRequest.body = resource.body; - fetchRequest.mode = resource.mode; - fetchRequest.credentials = resource.credentials; - fetchRequest.cache = resource.cache; - fetchRequest.redirect = resource.redirect; - fetchRequest.referrer = resource.referrer; - fetchRequest.referrerPolicy = resource.referrerPolicy; - fetchRequest.integrity = resource.integrity; - fetchRequest.keepalive = resource.keepalive; - } else { - fetchRequest.url = resource != null ? resource.toString() : null; - if (init != null) { - fetchRequest.method = init.method; - fetchRequest.headers = init.headers; - fetchRequest.body = init.body; - fetchRequest.mode = init.mode; - fetchRequest.credentials = init.credentials; - fetchRequest.cache = init.cache; - fetchRequest.redirect = init.redirect; - fetchRequest.referrer = init.referrer; - fetchRequest.referrerPolicy = init.referrerPolicy; - fetchRequest.integrity = init.integrity; - fetchRequest.keepalive = init.keepalive; - } - } - if (fetchRequest.headers instanceof Headers) { - fetchRequest.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers); - } - fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials); - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) { - fetchRequest.body = body; - return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { - if (result != null) { - switch (result.action) { - case 0: - var controller = new AbortController(); +public class InterceptFetchRequestJS { + + public static let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptFetchRequest" + } + + public static func INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) = true; + (function(fetch) { + if (fetch == null) { + return; + } + window.fetch = async function(resource, init) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == true) { + var fetchRequest = { + url: null, + method: null, + headers: null, + body: null, + mode: null, + credentials: null, + cache: null, + redirect: null, + referrer: null, + referrerPolicy: null, + integrity: null, + keepalive: null + }; + if (resource instanceof Request) { + fetchRequest.url = resource.url; + fetchRequest.method = resource.method; + fetchRequest.headers = resource.headers; + fetchRequest.body = resource.body; + fetchRequest.mode = resource.mode; + fetchRequest.credentials = resource.credentials; + fetchRequest.cache = resource.cache; + fetchRequest.redirect = resource.redirect; + fetchRequest.referrer = resource.referrer; + fetchRequest.referrerPolicy = resource.referrerPolicy; + fetchRequest.integrity = resource.integrity; + fetchRequest.keepalive = resource.keepalive; + } else { + fetchRequest.url = resource != null ? resource.toString() : null; if (init != null) { - init.signal = controller.signal; - } else { - init = { - signal: controller.signal - }; - } - controller.abort(); - break; - } - if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); - if (result.headers != null) { - result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; - } else { - result.headers = { 'Content-Type': formDataContentType }; + fetchRequest.method = init.method; + fetchRequest.headers = init.headers; + fetchRequest.body = init.body; + fetchRequest.mode = init.mode; + fetchRequest.credentials = init.credentials; + fetchRequest.cache = init.cache; + fetchRequest.redirect = init.redirect; + fetchRequest.referrer = init.referrer; + fetchRequest.referrerPolicy = init.referrerPolicy; + fetchRequest.integrity = init.integrity; + fetchRequest.keepalive = init.keepalive; } } + if (fetchRequest.headers instanceof Headers) { + fetchRequest.headers = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertHeadersToJson(fetchRequest.headers); + } + fetchRequest.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertCredentialsToJson(fetchRequest.credentials); + return \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(fetchRequest.body).then(function(body) { + fetchRequest.body = body; + return window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { + if (result != null) { + switch (result.action) { + case 0: + var controller = new AbortController(); + if (init != null) { + init.signal = controller.signal; + } else { + init = { + signal: controller.signal + }; + } + controller.abort(); + break; + } + if (result.body != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) && result.body.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.body); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + resource = result.url; + if (init == null) { + init = {}; + } + if (result.method != null && result.method.length > 0) { + init.method = result.method; + } + if (result.headers != null && Object.keys(result.headers).length > 0) { + init.headers = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToHeaders(result.headers); + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) || result.body == null) { + init.body = result.body; + } else if (result.body.length > 0) { + init.body = new Uint8Array(result.body); + } + if (result.mode != null && result.mode.length > 0) { + init.mode = result.mode; + } + if (result.credentials != null) { + init.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToCredential(result.credentials); + } + if (result.cache != null && result.cache.length > 0) { + init.cache = result.cache; + } + if (result.redirect != null && result.redirect.length > 0) { + init.redirect = result.redirect; + } + if (result.referrer != null && result.referrer.length > 0) { + init.referrer = result.referrer; + } + if (result.referrerPolicy != null && result.referrerPolicy.length > 0) { + init.referrerPolicy = result.referrerPolicy; + } + if (result.integrity != null && result.integrity.length > 0) { + init.integrity = result.integrity; + } + if (result.keepalive != null) { + init.keepalive = result.keepalive; + } + return fetch(resource, init); + } + return fetch(resource, init); + }); + }); + } else { + return fetch(resource, init); } - resource = result.url; - if (init == null) { - init = {}; - } - if (result.method != null && result.method.length > 0) { - init.method = result.method; - } - if (result.headers != null && Object.keys(result.headers).length > 0) { - init.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) { - init.body = result.body; - } else if (result.body.length > 0) { - init.body = new Uint8Array(result.body); - } - if (result.mode != null && result.mode.length > 0) { - init.mode = result.mode; - } - if (result.credentials != null) { - init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToCredential(result.credentials); - } - if (result.cache != null && result.cache.length > 0) { - init.cache = result.cache; - } - if (result.redirect != null && result.redirect.length > 0) { - init.redirect = result.redirect; - } - if (result.referrer != null && result.referrer.length > 0) { - init.referrer = result.referrer; - } - if (result.referrerPolicy != null && result.referrerPolicy.length > 0) { - init.referrerPolicy = result.referrerPolicy; - } - if (result.integrity != null && result.integrity.length > 0) { - init.integrity = result.integrity; - } - if (result.keepalive != null) { - init.keepalive = result.keepalive; - } - return fetch(resource, init); - } - return fetch(resource, init); - }); - }); - } else { - return fetch(resource, init); + }; + })(window.fetch); + """ } - }; -})(window.fetch); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift index 1549c28be..0e42a3734 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift @@ -7,237 +7,287 @@ import Foundation -let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" - -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: JAVASCRIPT_BRIDGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["callHandler"]) - -let JAVASCRIPT_BRIDGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME) = {}; -\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {}; -window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - var _callHandlerID = setTimeout(function(){}); - window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} ); - return new Promise(function(resolve, reject) { - window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject}; - }); -}; -\(WEB_MESSAGE_LISTENER_JS_SOURCE) -\(UTIL_JS_SOURCE) -""" - -let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; +public class JavaScriptBridgeJS { + private static var _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" + public static func set_JAVASCRIPT_BRIDGE_NAME(bridgeName: String) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName + } + public static func get_JAVASCRIPT_BRIDGE_NAME() -> String { + return _JAVASCRIPT_BRIDGE_NAME + } + + public static let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" + + public static let VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET" -let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util" + public static func JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: String, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + let source = JAVASCRIPT_BRIDGE_JS_SOURCE().replacingOccurrences(of: VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, with: expectedBridgeSecret) + return PluginScript( + groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: source, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: ["callHandler"]) + } -/* - https://github.com/github/fetch/blob/master/fetch.js - */ -let UTIL_JS_SOURCE = """ -\(JAVASCRIPT_UTIL_VAR_NAME) = { - support: { - searchParams: 'URLSearchParams' in window, - iterable: 'Symbol' in window && 'iterator' in Symbol, - blob: - 'FileReader' in window && - 'Blob' in window && - (function() { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - })(), - formData: 'FormData' in window, - arrayBuffer: 'ArrayBuffer' in window - }, - isDataView: function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj); - }, - fileReaderReady: function(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result); - }; - reader.onerror = function() { - reject(reader.error); - }; - }); - }, - readBlobAsArrayBuffer: function(blob) { - var reader = new FileReader(); - var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader); - reader.readAsArrayBuffer(blob); - return promise; - }, - convertBodyToArrayBuffer: function(body) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ]; - var isArrayBufferView = null; - if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) { - isArrayBufferView = - ArrayBuffer.isView || - function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + public static func JAVASCRIPT_BRIDGE_JS_SOURCE() -> String { + return """ + window.\(get_JAVASCRIPT_BRIDGE_NAME()) = {}; + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME()) = {}; + (function(window) { + var bridgeSecret = '\(VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET)'; + var _JSON_stringify; + var _Array_slice; + var _setTimeout; + var _Promise; + var _UserMessageHandler; + var _postMessage; + try { + _JSON_stringify = window.JSON.stringify; + _Array_slice = window.Array.prototype.slice; + _Array_slice.call = window.Function.prototype.call; + _setTimeout = window.setTimeout; + _Promise = window.Promise; + _UserMessageHandler = window.webkit.messageHandlers['callHandler']; + _postMessage = _UserMessageHandler.postMessage; + _postMessage.call = window.Function.prototype.call; + } catch (_) { return; } + window.\(get_JAVASCRIPT_BRIDGE_NAME()).callHandler = function() { + var _windowId = \(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()); + var _callHandlerID = _setTimeout(function(){}); + _postMessage.call(_UserMessageHandler, { + 'handlerName': arguments[0], + '_callHandlerID': _callHandlerID, + '_bridgeSecret': bridgeSecret, + 'args': _JSON_stringify(_Array_slice.call(arguments, 1)), + '_windowId': _windowId + }); + return new _Promise(function(resolve, reject) { + try { + (window.top === window ? window : window.top).\(get_JAVASCRIPT_BRIDGE_NAME())[_callHandlerID] = {resolve: resolve, reject: reject}; + } catch (e) { + resolve(); + } + }); }; - } + })(window); + \(WebMessageListenerJS.WEB_MESSAGE_LISTENER_JS_SOURCE()) + \(UTIL_JS_SOURCE()) + """ + } - var bodyUsed = false; + public static let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; - this._bodyInit = body; - if (!body) { - this._bodyText = ''; - } else if (typeof body === 'string') { - this._bodyText = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString(); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer); - this._bodyInit = new Blob([this._bodyArrayBuffer]); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body); - } else { - this._bodyText = body = Object.prototype.toString.call(body); - } + public static func JAVASCRIPT_UTIL_VAR_NAME() -> String { + return "window.\(get_JAVASCRIPT_BRIDGE_NAME())._Util" + } - this.blob = function () { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob); - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])); - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob'); - } else { - return Promise.resolve(new Blob([this._bodyText])); + /* + https://github.com/github/fetch/blob/master/fetch.js + */ + public static func UTIL_JS_SOURCE() -> String { + return """ + \(JAVASCRIPT_UTIL_VAR_NAME()) = { + support: { + searchParams: 'URLSearchParams' in window, + iterable: 'Symbol' in window && 'iterator' in Symbol, + blob: + 'FileReader' in window && + 'Blob' in window && + (function() { + try { + new Blob(); + return true; + } catch (e) { + return false; + } + })(), + formData: 'FormData' in window, + arrayBuffer: 'ArrayBuffer' in window + }, + isDataView: function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj); + }, + fileReaderReady: function(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }); + }, + readBlobAsArrayBuffer: function(blob) { + var reader = new FileReader(); + var promise = \(JAVASCRIPT_UTIL_VAR_NAME()).fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise; + }, + convertBodyToArrayBuffer: function(body) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + var isArrayBufferView = null; + if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer) { + isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + }; + } + + var bodyUsed = false; + + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME()).isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + this.blob = function () { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob); + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])); + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob'); + } else { + return Promise.resolve(new Blob([this._bodyText])); + } + }; + + if (this._bodyArrayBuffer) { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ); + } else { + return Promise.resolve(this._bodyArrayBuffer); + } + } + return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME()).readBlobAsArrayBuffer); + }, + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + convertBodyRequest: function(body) { + if (body == null) { + return new Promise((resolve, reject) => resolve(null)); + } + if (\(JAVASCRIPT_UTIL_VAR_NAME()).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && body instanceof URLSearchParams)) { + return new Promise((resolve, reject) => resolve(body.toString())); + } + if (window.Response != null) { + return new Response(body).arrayBuffer().then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + } + return \(JAVASCRIPT_UTIL_VAR_NAME()).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + }, + arrayBufferToString: function(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') >= 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.substr(2, 40); + return 'multipart/form-data; boundary=' + boundary; + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + for (var header of headers.keys()) { + var value = headers.get(header); + headersObj[header] = value; + } + return headersObj; + }, + convertJsonToHeaders: function(headersJson) { + return new Headers(headersJson); + }, + convertCredentialsToJson: function(credentials) { + var credentialsObj = {}; + if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.protocol = credentials.protocol; + credentialsObj.provider = credentials.provider; + credentialsObj.iconURL = credentials.iconURL; + } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.password = credentials.password; + credentialsObj.iconURL = credentials.iconURL; + } else { + credentialsObj.type = 'default'; + credentialsObj.value = credentials; + } + return credentialsObj; + }, + convertJsonToCredential: function(credentialsJson) { + var credentials; + if (window.FederatedCredential != null && credentialsJson.type === 'federated') { + credentials = new FederatedCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + protocol: credentialsJson.protocol, + provider: credentialsJson.provider, + iconURL: credentialsJson.iconURL + }); + } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { + credentials = new PasswordCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + password: credentialsJson.password, + iconURL: credentialsJson.iconURL + }); + } else { + credentials = credentialsJson.value == null ? undefined : credentialsJson.value; + } + return credentials; } }; - - if (this._bodyArrayBuffer) { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (ArrayBuffer.isView(this._bodyArrayBuffer)) { - return Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer.byteOffset, - this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength - ) - ); - } else { - return Promise.resolve(this._bodyArrayBuffer); - } - } - return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer); - }, - isString: function(variable) { - return typeof variable === 'string' || variable instanceof String; - }, - convertBodyRequest: function(body) { - if (body == null) { - return new Promise((resolve, reject) => resolve(null)); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) { - return new Promise((resolve, reject) => resolve(body.toString())); - } - if (window.Response != null) { - return new Response(body).arrayBuffer().then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - } - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - }, - arrayBufferToString: function(arrayBuffer) { - var uint8Array = new Uint8Array(arrayBuffer); - return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); - }, - isBodyFormData: function(bodyString) { - return bodyString.indexOf('------WebKitFormBoundary') >= 0; - }, - getFormDataContentType: function(bodyString) { - var boundary = bodyString.substr(2, 40); - return 'multipart/form-data; boundary=' + boundary; - }, - convertHeadersToJson: function(headers) { - var headersObj = {}; - for (var header of headers.keys()) { - var value = headers.get(header); - headersObj[header] = value; - } - return headersObj; - }, - convertJsonToHeaders: function(headersJson) { - return new Headers(headersJson); - }, - convertCredentialsToJson: function(credentials) { - var credentialsObj = {}; - if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.protocol = credentials.protocol; - credentialsObj.provider = credentials.provider; - credentialsObj.iconURL = credentials.iconURL; - } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.password = credentials.password; - credentialsObj.iconURL = credentials.iconURL; - } else { - credentialsObj.type = 'default'; - credentialsObj.value = credentials; - } - return credentialsObj; - }, - convertJsonToCredential: function(credentialsJson) { - var credentials; - if (window.FederatedCredential != null && credentialsJson.type === 'federated') { - credentials = new FederatedCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - protocol: credentialsJson.protocol, - provider: credentialsJson.provider, - iconURL: credentialsJson.iconURL - }); - } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { - credentials = new PasswordCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - password: credentialsJson.password, - iconURL: credentialsJson.iconURL - }); - } else { - credentials = credentialsJson.value == null ? undefined : credentialsJson.value; - } - return credentials; + """ } -}; -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift index ef2e7d677..9cdcdd5a5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/LastTouchedAnchorOrImageJS.swift @@ -7,56 +7,65 @@ import Foundation -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT" - -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null; -window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null; -(function() { - document.addEventListener('touchstart', function(event) { - var target = event.target; - while (target) { - if (target.tagName === 'IMG') { - var img = target; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = { - url: img.src - }; - var parent = img.parentNode; - while (parent) { - if (parent.tagName === 'A') { - window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = { - title: parent.textContent, - url: parent.href, - src: img.src +public class LastTouchedAnchorOrImageJS { + + public static let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = null; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = null; + (function() { + document.addEventListener('touchstart', function(event) { + var target = event.target; + while (target) { + if (target.tagName === 'IMG') { + var img = target; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = { + url: img.src + }; + var parent = img.parentNode; + while (parent) { + if (parent.tagName === 'A') { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = { + title: parent.textContent, + url: parent.href, + src: img.src + }; + break; + } + parent = parent.parentNode; + } + return; + } else if (target.tagName === 'A') { + var link = target; + var images = link.getElementsByTagName('img'); + var img = (images.length > 0) ? images[0] : null; + var imgSrc = (img != null) ? img.src : null; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastImageTouched; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._lastAnchorOrImageTouched = { + title: link.textContent, + url: link.href, + src: imgSrc }; - break; + return; } - parent = parent.parentNode; + target = target.parentNode; } - return; - } else if (target.tagName === 'A') { - var link = target; - var images = link.getElementsByTagName('img'); - var img = (images.length > 0) ? images[0] : null; - var imgSrc = (img != null) ? img.src : null; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched; - window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = { - title: link.textContent, - url: link.href, - src: imgSrc - }; - return; - } - target = target.parentNode; - } - }); -})(); -""" + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift index 828de9ef5..18a5e496b 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnLoadResourceJS.swift @@ -7,33 +7,44 @@ import Foundation -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource" - -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_LOAD_RESOURCE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_LOAD_RESOURCE_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true; -(function() { - var observer = new PerformanceObserver(function(list) { - list.getEntries().forEach(function(entry) { - if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) { - var resource = { - "url": entry.name, - "initiatorType": entry.initiatorType, - "startTime": entry.startTime, - "duration": entry.duration - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource); - } - }); - }); - observer.observe({entryTypes: ['resource']}); -})(); -""" +public class OnLoadResourceJS { + + public static let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnLoadResource" + } + + public static func ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_LOAD_RESOURCE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) = true; + (function() { + var observer = new PerformanceObserver(function(list) { + list.getEntries().forEach(function(entry) { + if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == true) { + var resource = { + "url": entry.name, + "initiatorType": entry.initiatorType, + "startTime": entry.startTime, + "duration": entry.duration + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onLoadResource", resource); + } + }); + }); + observer.observe({entryTypes: ['resource']}); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift index 345b5eced..d9221d10e 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_WINDOW_BLUR_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('blur', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur'); - }); -})(); -""" +public class OnWindowBlurEventJS { + + public static let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_BLUR_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('blur', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowBlur'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift index 9ebda3e96..b08bb0889 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('focus', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus'); - }); -})(); -""" +public class OnWindowFocusEventJS { + + public static let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_FOCUS_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('focus', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowFocus'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift index a1c6637d7..d84c14ee3 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift @@ -7,25 +7,34 @@ import Foundation -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = ""; -(function() { - var metaTagNodes = document.head.getElementsByTagName('meta'); - for (var i = 0; i < metaTagNodes.length; i++) { - var metaTagNode = metaTagNodes[i]; - if (metaTagNode.name === "viewport") { - window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content; - } +public class OriginalViewPortMetaTagContentJS { + + public static let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE(), + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = ""; + (function() { + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + if (metaTagNode.name === "viewport") { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = metaTagNode.content; + } + } + })(); + """ } -})(); -""" +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift index 5ac12691f..fbf29f50f 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PrintJS.swift @@ -7,18 +7,26 @@ import Foundation -let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" - -let PRINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PRINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let PRINT_JS_SOURCE = """ -window.print = function() { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href); +public class PrintJS { + + public static let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" + + public static func PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PRINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func PRINT_JS_SOURCE() -> String { + return """ + window.print = function() { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onPrintRequest", window.location.href); + } + """ + } } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift index d2ef3e2a5..27c076be2 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/PromisePolyfillJS.swift @@ -8,20 +8,25 @@ import Foundation import WebKit -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT" - -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PROMISE_POLYFILL_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -// https://github.com/tildeio/rsvp.js -let PROMISE_POLYFILL_JS_SOURCE = """ -if (window.Promise == null) { - !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PROMISE_POLYFILL_JS_SOURCE, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + // https://github.com/tildeio/rsvp.js + public static let PROMISE_POLYFILL_JS_SOURCE = """ + if (window.Promise == null) { + !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: NOT_SUPPORT_ZOOM_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let NOT_SUPPORT_ZOOM_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func SUPPORT_ZOOM_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift index 184458413..e41494d88 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift @@ -7,4 +7,10 @@ import Foundation -let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels" +public class WebMessageChannelJS { + + public static func WEB_MESSAGE_CHANNELS_VARIABLE_NAME() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._webMessageChannels" + } + +} diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift index ab8edfaed..6740765ac 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift @@ -7,110 +7,37 @@ import Foundation -let WEB_MESSAGE_LISTENER_JS_SOURCE = """ -function FlutterInAppWebViewWebMessageListener(jsObjectName) { - this.jsObjectName = jsObjectName; - this.listeners = []; - this.onmessage = null; -} -FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) { - var message = { - "data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null), - "type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0 - }; - window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); -}; -FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { - if (listener == null) { - return; - } - this.listeners.push(listener); -}; -FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) { - if (listener == null) { - return; - } - var index = this.listeners.indexOf(listener); - if (index >= 0) { - this.listeners.splice(index, 1); - } -}; - -window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) { - // replace ipv4 address if any - var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); - if (ipv4) { - ip_string = ipv4[1]; - ipv4 = ipv4[2].match(/[0-9]+/g); - for (var i = 0;i < 4;i ++) { - var byte = parseInt(ipv4[i],10); - ipv4[i] = ("0" + byte.toString(16)).substr(-2); - } - ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; - } - - // take care of leading and trailing :: - ip_string = ip_string.replace(/^:|:$/g, ''); - - var ipv6 = ip_string.split(':'); - - for (var i = 0; i < ipv6.length; i ++) { - var hex = ipv6[i]; - if (hex != "") { - // normalize leading zeros - ipv6[i] = ("0000" + hex).substr(-4); - } - else { - // normalize grouped zeros :: - hex = []; - for (var j = ipv6.length; j <= 8; j ++) { - hex.push('0000'); +public class WebMessageListenerJS { + + public static func WEB_MESSAGE_LISTENER_JS_SOURCE() -> String { + return """ + function FlutterInAppWebViewWebMessageListener(jsObjectName) { + this.jsObjectName = jsObjectName; + this.listeners = []; + this.onmessage = null; + } + FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) { + var message = { + "data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null), + "type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0 + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessageListenerPostMessageReceived', {jsObjectName: this.jsObjectName, message: message}); + }; + FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { + if (listener == null) { + return; } - ipv6[i] = hex.join(':'); - } - } - - return ipv6.join(':'); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) { - for (var rule of allowedOriginRules) { - if (rule === "*") { - return true; - } - if (scheme == null || scheme === "") { - continue; - } - if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { - continue; - } - var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; - var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; - var IPv6 = null; - if (rule.host != null && rule.host[0] === "[") { - try { - IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); - } catch {} - } - var hostIPv6 = null; - try { - hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host); - } catch {} - - var schemeAllowed = scheme == rule.scheme; - - var hostAllowed = rule.host == null || - rule.host === "" || - host === rule.host || - (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || - (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); - - var portAllowed = rulePort === currentPort; - - if (schemeAllowed && hostAllowed && portAllowed) { - return true; - } + this.listeners.push(listener); + }; + FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) { + if (listener == null) { + return; + } + var index = this.listeners.indexOf(listener); + if (index >= 0) { + this.listeners.splice(index, 1); + } + }; + """ } - return false; } -""" diff --git a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift index 8b3a1879f..173167416 100644 --- a/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift +++ b/flutter_inappwebview_ios/ios/Classes/PluginScriptsJS/WindowIdJS.swift @@ -7,13 +7,20 @@ import Foundation -let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" - -let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId" - -let WINDOW_ID_INITIALIZE_JS_SOURCE = """ -(function() { - \(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); - return \(WINDOW_ID_VARIABLE_JS_SOURCE); -})() -""" +public class WindowIdJS { + + public static let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" + + public static func WINDOW_ID_VARIABLE_JS_SOURCE() -> String { + return "window._\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_windowId" + } + + public static func WINDOW_ID_INITIALIZE_JS_SOURCE() -> String { + return """ + (function() { + \(WINDOW_ID_VARIABLE_JS_SOURCE()) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); + return \(WINDOW_ID_VARIABLE_JS_SOURCE()); + })() + """ + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift index 337fae3a7..6309353fc 100644 --- a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift +++ b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobChannelDelegate.swift @@ -63,6 +63,7 @@ public class PrintJobChannelDelegate: ChannelDelegate { } deinit { + debugPrint("PrintJobChannelDelegate - dealloc") dispose() } } diff --git a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift index 6b4c3210e..1e0ce08eb 100644 --- a/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift +++ b/flutter_inappwebview_ios/ios/Classes/PrintJob/PrintJobController.swift @@ -36,11 +36,9 @@ public class PrintJobController: NSObject, Disposable, UIPrintInteractionControl self.printFormatter = job?.printFormatter self.printPageRenderer = job?.printPageRenderer self.job?.delegate = self - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) } public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) { @@ -99,4 +97,8 @@ public class PrintJobController: NSObject, Disposable, UIPrintInteractionControl plugin?.printJobManager?.jobs[id] = nil plugin = nil } + + deinit { + debugPrint("PrintJobController - dealloc") + } } diff --git a/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift b/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift new file mode 100644 index 000000000..5c2dade06 --- /dev/null +++ b/flutter_inappwebview_ios/ios/Classes/ProxyManager.swift @@ -0,0 +1,247 @@ +// +// ProxyManager.swift +// flutter_inappwebview +// + +import Foundation +import WebKit + +@available(iOS 17.0, *) +public class ProxyManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_proxycontroller" + + private var plugin: SwiftFlutterPlugin? + + init(plugin: SwiftFlutterPlugin) { + super.init(channel: FlutterMethodChannel(name: ProxyManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) + self.plugin = plugin + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? [String: Any] + switch call.method { + case "setProxyOverride": + if let args = arguments?["settings"] as? [String:Any?], + let settings = ProxySettings.fromMap(map: args) { + setProxyOverride(settings) + result(true) + } else { + result(false) + } + break + case "clearProxyOverride": + clearProxyOverride() + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func setProxyOverride(_ settings: ProxySettings) { + let proxyConfigurations = settings.toProxyConfigurations() + WKWebsiteDataStore.default().proxyConfigurations = proxyConfigurations + WKWebsiteDataStore.nonPersistent().proxyConfigurations = proxyConfigurations + } + + public func clearProxyOverride() { + WKWebsiteDataStore.default().proxyConfigurations = [] + WKWebsiteDataStore.nonPersistent().proxyConfigurations = [] + } + + public override func dispose() { + super.dispose() + plugin = nil + } + + deinit { + debugPrint("ProxyManager - dealloc") + dispose() + } +} + +@available(iOS 17.0, *) +public class ProxySettings { + var proxyRules: [ProxyRule] + + init( + proxyRules: [ProxyRule] + ) { + self.proxyRules = proxyRules + } + + public static func fromMap(map: [String:Any?]?) -> ProxySettings? { + guard let map = map else { + return nil + } + return ProxySettings( + proxyRules: (map["proxyRules"] as! [[String:Any?]]).map { ProxyRule.fromMap(map: $0)! } + ) + } + + public func toProxyConfigurations() -> [ProxyConfiguration] { + var proxyConfigurations: [ProxyConfiguration] = [] + for rule in proxyRules { + if let proxyConfiguration = rule.toProxyConfiguration() { + proxyConfigurations.append(proxyConfiguration) + } + } + return proxyConfigurations + } +} + +@available(iOS 17.0, *) +public class ProxyRule { + var url: String + var allowFailover: Bool? + var excludedDomains: [String]? + var matchDomains: [String]? + var username: String? + var password: String? + var relayHop1: ProxyRelayHop? + var relayHop2: ProxyRelayHop? + + init( + url: String, + allowFailover: Bool?, + excludedDomains: [String]?, + matchDomains: [String]?, + username: String?, + password: String?, + relayHop1: ProxyRelayHop?, + relayHop2: ProxyRelayHop? + ) { + self.url = url + self.allowFailover = allowFailover + self.excludedDomains = excludedDomains + self.matchDomains = matchDomains + self.username = username + self.password = password + self.relayHop1 = relayHop1 + self.relayHop2 = relayHop2 + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRule? { + guard let map = map else { + return nil + } + return ProxyRule( + url: map["url"] as! String, + allowFailover: map["allowFailover"] as? Bool, + excludedDomains: map["excludedDomains"] as? [String], + matchDomains: map["matchDomains"] as? [String], + username: map["username"] as? String, + password: map["password"] as? String, + relayHop1: ProxyRelayHop.fromMap(map: map["relayHop1"] as? [String:Any?]), + relayHop2: ProxyRelayHop.fromMap(map: map["relayHop2"] as? [String:Any?]) + ) + } + + public func toProxyConfiguration() -> ProxyConfiguration? { + guard let endpointUrl = URL(string: url.contains("://") ? url : "http://" + url), + let port: NWEndpoint.Port = .init(rawValue: UInt16(endpointUrl.port ?? 80)), + let host = endpointUrl.host else { + return nil + } + + var endpointHost = NWEndpoint.Host(host) + if let ipv4 = IPv4Address(host) { + endpointHost = .ipv4(ipv4) + } else if let ipv6 = IPv6Address(host) { + endpointHost = .ipv6(ipv6) + } + let endpoint = NWEndpoint.hostPort(host: endpointHost, port: port) + var proxyConfiguration: ProxyConfiguration + let proxyRelayHops: [ProxyRelayHop] = [relayHop1, relayHop2].filter({ $0 != nil }).map({ $0! }) + if !proxyRelayHops.isEmpty { + proxyConfiguration = ProxyConfiguration(relayHops: proxyRelayHops.compactMap({ $0.toRelayHop() })) + } else { + proxyConfiguration = endpointUrl.scheme?.lowercased() == "socks5" ? + ProxyConfiguration(socksv5Proxy: endpoint) : + ProxyConfiguration(httpCONNECTProxy: endpoint, tlsOptions: endpointUrl.scheme?.lowercased() == "https" ? .init() : nil) + } + + if let allowFailover = allowFailover { + proxyConfiguration.allowFailover = allowFailover + } + if let excludedDomains = excludedDomains { + proxyConfiguration.excludedDomains = excludedDomains + } + if let matchDomains = matchDomains { + proxyConfiguration.matchDomains = matchDomains + } + if let username = username, let password = password { + proxyConfiguration.applyCredential(username: username, password: password) + } + return proxyConfiguration + } +} + +@available(iOS 17.0, *) +public class ProxyRelayHop { + var http3RelayEndpoint: String? + var http2RelayEndpoint: String? + var additionalHTTPHeaders: [String:String]? + + init( + http3RelayEndpoint: String, + http2RelayEndpoint: String?, + additionalHTTPHeaders: [String:String]? + ) { + self.http3RelayEndpoint = http3RelayEndpoint + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + init( + http2RelayEndpoint: String, + additionalHTTPHeaders: [String:String]? + ) { + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRelayHop? { + guard let map = map else { + return nil + } + let http3RelayEndpoint = map["http3RelayEndpoint"] as? String + let http2RelayEndpoint = map["http2RelayEndpoint"] as? String + let additionalHTTPHeaders = map["additionalHTTPHeaders"] as? [String:String] + if http3RelayEndpoint == nil, http2RelayEndpoint == nil { + return nil + } + if http3RelayEndpoint != nil { + return ProxyRelayHop( + http3RelayEndpoint: http3RelayEndpoint!, + http2RelayEndpoint: http2RelayEndpoint, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + return ProxyRelayHop( + http2RelayEndpoint: http2RelayEndpoint!, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + + public func toRelayHop() -> ProxyConfiguration.RelayHop? { + if let http3RelayEndpoint = http3RelayEndpoint, + let url = URL(string: http3RelayEndpoint) { + var http2Endpoint: NWEndpoint? = nil + if let http2RelayEndpoint = http2RelayEndpoint, + let url2 = URL(string: http2RelayEndpoint) { + http2Endpoint = NWEndpoint.url(url2) + } + return ProxyConfiguration.RelayHop(http3RelayEndpoint: NWEndpoint.url(url), + http2RelayEndpoint: http2Endpoint, + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + if let http2RelayEndpoint = http2RelayEndpoint, + let url = URL(string: http2RelayEndpoint) { + return ProxyConfiguration.RelayHop(http2RelayEndpoint: NWEndpoint.url(url), + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + return nil + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift b/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift index 5df23c173..3cf19bfc5 100644 --- a/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift +++ b/flutter_inappwebview_ios/ios/Classes/PullToRefresh/PullToRefreshControl.swift @@ -21,11 +21,9 @@ public class PullToRefreshControl: UIRefreshControl, Disposable { super.init() self.plugin = plugin self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger()) + self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) } required init?(coder: NSCoder) { diff --git a/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index 5887ea77e..805d422c9 100755 --- a/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -19,7 +19,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate { var prewarmingTokens: [String: Any?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift b/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift index 980e983e4..e71fa9827 100755 --- a/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift +++ b/flutter_inappwebview_ios/ios/Classes/SafariViewController/SafariViewController.swift @@ -26,7 +26,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle SafariViewController.prepareConfig(configuration: configuration, safariSettings: safariSettings) super.init(url: url, configuration: configuration) let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel) self.delegate = self } @@ -38,7 +38,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle self.safariSettings = safariSettings super.init(url: url, entersReaderIfAvailable: entersReaderIfAvailable) let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel) self.delegate = self } diff --git a/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift b/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift index 22eaecdcd..99e49ddcb 100755 --- a/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift +++ b/flutter_inappwebview_ios/ios/Classes/SwiftFlutterPlugin.swift @@ -24,7 +24,7 @@ import SafariServices public class SwiftFlutterPlugin: NSObject, FlutterPlugin { - var registrar: FlutterPluginRegistrar? + var registrar: FlutterPluginRegistrar var platformUtil: PlatformUtil? var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? @@ -35,14 +35,16 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var chromeSafariBrowserManager: ChromeSafariBrowserManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? var printJobManager: PrintJobManager? + var proxyManager: Any? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] public init(with registrar: FlutterPluginRegistrar) { + self.registrar = registrar + super.init() - self.registrar = registrar registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) @@ -59,6 +61,9 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) printJobManager = PrintJobManager(plugin: self) + if #available(iOS 17.0, *) { + proxyManager = ProxyManager(plugin: self) + } } public static func register(with registrar: FlutterPluginRegistrar) { @@ -79,16 +84,20 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { credentialDatabase?.dispose() credentialDatabase = nil if #available(iOS 11.0, *) { - (myCookieManager as! MyCookieManager?)?.dispose() + (myCookieManager as? MyCookieManager)?.dispose() myCookieManager = nil } if #available(iOS 9.0, *) { - (myWebStorageManager as! MyWebStorageManager?)?.dispose() + (myWebStorageManager as? MyWebStorageManager)?.dispose() myWebStorageManager = nil } webAuthenticationSessionManager?.dispose() webAuthenticationSessionManager = nil printJobManager?.dispose() printJobManager = nil + if #available(iOS 17.0, *) { + (proxyManager as? ProxyManager)?.dispose() + proxyManager = nil + } } } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift b/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift new file mode 100644 index 000000000..71334a106 --- /dev/null +++ b/flutter_inappwebview_ios/ios/Classes/Types/JavaScriptHandlerFunctionData.swift @@ -0,0 +1,42 @@ +// +// JavaScriptHandlerFunctionData.swift +// Pods +// +// Created by Lorenzo Pichilli on 27/10/24. +// + +public class JavaScriptHandlerFunctionData: NSObject { + var args: String + var isMainFrame: Bool + var origin: String + var requestUrl: String + + public init(args: String, isMainFrame: Bool, origin: String, requestUrl: String) { + self.args = args + self.isMainFrame = isMainFrame + self.origin = origin + self.requestUrl = requestUrl + } + + public static func fromMap(map: [String:Any?]?) -> JavaScriptHandlerFunctionData? { + guard let map = map else { + return nil + } + + return JavaScriptHandlerFunctionData( + args: map["args"] as! String, + isMainFrame: map["isMainFrame"] as! Bool, + origin: map["origin"] as! String, + requestUrl: map["requestUrl"] as! String + ) + } + + public func toMap () -> [String:Any?] { + return [ + "args": args, + "isMainFrame": isMainFrame, + "origin": origin, + "requestUrl": requestUrl + ] + } +} diff --git a/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift b/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift index 9b6499f24..591702741 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/PluginScript.swift @@ -16,8 +16,8 @@ public class PluginScript: UserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -36,8 +36,9 @@ public class PluginScript: UserScript { } @available(iOS 14.0, *) - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, + allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -47,6 +48,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, requiredInAllContentWorlds: Bool? = nil, + allowedOriginRules: [String]? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { if #available(iOS 14.0, *) { return PluginScript( @@ -55,6 +57,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -64,6 +67,7 @@ public class PluginScript: UserScript { source: source ?? self.source, injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -75,6 +79,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, contentWorld: WKContentWorld? = nil, + allowedOriginRules: [String]? = nil, requiredInAllContentWorlds: Bool? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { return PluginScript( @@ -83,6 +88,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: contentWorld ?? self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -95,6 +101,7 @@ public class PluginScript: UserScript { lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && lhs.contentWorld == rhs.contentWorld && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } else { @@ -102,6 +109,7 @@ public class PluginScript: UserScript { lhs.source == rhs.source && lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift b/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift index e18c588de..c12160cb5 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/UserScript.swift @@ -10,6 +10,7 @@ import WebKit public class UserScript: WKUserScript { var groupName: String? + var allowedOriginRules: [String]? private var contentWorldWrapper: Any? @available(iOS 14.0, *) @@ -27,9 +28,10 @@ public class UserScript: WKUserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) self.groupName = groupName + self.allowedOriginRules = allowedOriginRules } @available(iOS 14.0, *) @@ -39,10 +41,34 @@ public class UserScript: WKUserScript { } @available(iOS 14.0, *) - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) self.groupName = groupName self.contentWorld = contentWorld + self.allowedOriginRules = allowedOriginRules + } + + static private func wrapSourceCodeAddChecks(source: String, allowedOriginRules: [String]?) -> String { + var ifStatement = "if (" + if let allowedOriginRules = allowedOriginRules, !allowedOriginRules.contains("*") { + if allowedOriginRules.isEmpty { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return "" + } + var jsRegExpArray = "[" + for allowedOriginRule in allowedOriginRules { + if jsRegExpArray.count > 1 { + jsRegExpArray += "," + } + jsRegExpArray += "new RegExp('\(allowedOriginRule.replacingOccurrences(of: "\'", with: "\\'"))')" + } + if jsRegExpArray.count > 1 { + jsRegExpArray += "]" + ifStatement += "\(jsRegExpArray).some(function(rx) { return rx.test(window.location.origin); })" + } + } + return ifStatement.count > 4 ? "\(ifStatement)) { \(source) }" : source } public static func fromMap(map: [String:Any?]?, windowId: Int64?) -> UserScript? { @@ -58,14 +84,16 @@ public class UserScript: WKUserScript { source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, forMainFrameOnly: map["forMainFrameOnly"] as! Bool, - in: contentWorld + in: contentWorld, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } return UserScript( groupName: map["groupName"] as? String, source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, - forMainFrameOnly: map["forMainFrameOnly"] as! Bool + forMainFrameOnly: map["forMainFrameOnly"] as! Bool, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift b/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift index 633f1e1b5..c6c78b097 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/WKUserContentController.swift @@ -166,7 +166,7 @@ extension WKUserContentController { } } if let windowId = contentWorld.windowId { - generatedCode += "\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(String(windowId));\n" + generatedCode += "\(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()) = \(String(windowId));\n" } return generatedCode + "\n" + source } diff --git a/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift b/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift index b7a6e53ca..7cb2f3b58 100644 --- a/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift +++ b/flutter_inappwebview_ios/ios/Classes/Types/WebMessagePort.swift @@ -33,10 +33,10 @@ public class WebMessagePort: NSObject { let index = name == "port1" ? 0 : 1 webView.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).onmessage = function (event) { - window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessagePortMessageReceived', { "webMessageChannelId": "\(webMessageChannel.id)", "index": \(String(index)), "message": { @@ -74,14 +74,14 @@ public class WebMessagePort: NSObject { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString)); } @@ -104,7 +104,7 @@ public class WebMessagePort: NSObject { if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView { let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).close(); } diff --git a/flutter_inappwebview_ios/ios/Classes/Util.swift b/flutter_inappwebview_ios/ios/Classes/Util.swift index 9992ece9e..25be4af12 100644 --- a/flutter_inappwebview_ios/ios/Classes/Util.swift +++ b/flutter_inappwebview_ios/ios/Classes/Util.swift @@ -12,16 +12,16 @@ var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] public class Util { public static func getUrlAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> URL { - guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), - let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { + let key = plugin.registrar.lookupKey(forAsset: assetFilePath) + guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetURL } public static func getAbsPathAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> String { - guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), - let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { + let key = plugin.registrar.lookupKey(forAsset: assetFilePath) + guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetAbsPath diff --git a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index 99409b11d..3f8c32f6d 100644 --- a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -37,7 +37,7 @@ public class WebAuthenticationSession: NSObject, ASWebAuthenticationPresentation self.session = SFAuthenticationSession(url: self.url, callbackURLScheme: self.callbackURLScheme, completionHandler: self.completionHandler) } let channel = FlutterMethodChannel(name: WebAuthenticationSession.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger()) + binaryMessenger: plugin.registrar.messenger()) self.channelDelegate = WebAuthenticationSessionChannelDelegate(webAuthenticationSession: self, channel: channel) } diff --git a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 6ba8238d5..66eff8523 100644 --- a/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/flutter_inappwebview_ios/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -18,7 +18,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger())) self.plugin = plugin } diff --git a/flutter_inappwebview_ios/lib/src/cookie_manager.dart b/flutter_inappwebview_ios/lib/src/cookie_manager.dart index e640c9f02..2c0eea9cd 100755 --- a/flutter_inappwebview_ios/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_ios/lib/src/cookie_manager.dart @@ -407,13 +407,11 @@ class IOSCookieManager extends PlatformCookieManager with ChannelController { Future _getCookieExpirationDate(int expiresDate) async { var platformUtil = PlatformUtil.instance(); var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc(); - return !kIsWeb - ? await platformUtil.formatDate( - date: dateTime, - format: 'EEE, dd MMM yyyy hh:mm:ss z', - locale: 'en_US', - timezone: 'GMT') - : await platformUtil.getWebCookieExpirationDate(date: dateTime); + return await platformUtil.formatDate( + date: dateTime, + format: 'EEE, dd MMM yyyy HH:mm:ss z', + locale: 'en_US', + timezone: 'GMT'); } Future _shouldUseJavascript() async { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart index 0e9da6539..174329328 100644 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/headless_in_app_webview.dart @@ -32,8 +32,10 @@ class IOSHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -150,6 +152,7 @@ class IOSHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -359,13 +362,23 @@ class IOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart index 235d4ed17..e2b9d622d 100755 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; -import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// @@ -36,8 +35,10 @@ class IOSInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -160,6 +161,7 @@ class IOSInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -363,15 +365,24 @@ class IOSInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart index 7e847f14f..f807fb8ff 100644 --- a/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_ios/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,21 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [IOSInAppWebViewController]. /// @@ -66,8 +48,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; // List of properties to be saved and restored for keep alive feature - Map _javaScriptHandlersMap = - HashMap(); + Map _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -359,11 +340,11 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -371,20 +352,25 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1404,17 +1390,22 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1436,7 +1427,8 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1453,43 +1445,46 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxProgress(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1515,7 +1510,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1523,7 +1518,7 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1535,7 +1530,19 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + var jsHandlerResult = null; + if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerCallback) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerCallback)(handlerData.args); + } else if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerFunction) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerFunction)(handlerData); + } else { + jsHandlerResult = await _javaScriptHandlersMap[handlerName]!(); + } + return jsonEncode(jsHandlerResult); } catch (error, stacktrace) { developer.log(error.toString() + '\n' + stacktrace.toString(), name: 'JavaScript Handler "$handlerName"'); @@ -1975,16 +1982,14 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2234,6 +2239,30 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('clearFocus', args); } + @override + Future setInputMethodEnabled(bool enabled) async { + Map args = {}; + args.putIfAbsent("enabled", () => enabled); + return await channel?.invokeMethod('setInputMethodEnabled', args); + } + + @override + Future hideInputMethod() async { + Map args = {}; + return await channel?.invokeMethod('hideInputMethod', args); + } + + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + @override Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; @@ -2682,6 +2711,19 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController await channel?.invokeMethod('loadSimulatedRequest', args); } + @override + Future saveState() async { + Map args = {}; + return await channel?.invokeMethod('saveState', args); + } + + @override + Future restoreState(Uint8List? state) async { + Map args = {}; + args.putIfAbsent('state', () => state); + return await channel?.invokeMethod('restoreState', args) ?? false; + } + @override Future getDefaultUserAgent() async { Map args = {}; @@ -2712,6 +2754,23 @@ class IOSInAppWebViewController extends PlatformInAppWebViewController await _staticChannel.invokeMethod('clearAllCache', args); } + @override + Future setJavaScriptBridgeName(String bridgeName) async { + assert(RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName), + 'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.'); + Map args = {}; + args.putIfAbsent('bridgeName', () => bridgeName); + await _staticChannel.invokeMethod('setJavaScriptBridgeName', args); + } + + @override + Future getJavaScriptBridgeName() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getJavaScriptBridgeName', args) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart index fc27a56db..91139fd42 100644 --- a/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_ios/lib/src/inappwebview_platform.dart @@ -11,6 +11,7 @@ import 'pull_to_refresh/main.dart'; import 'web_message/main.dart'; import 'web_storage/main.dart'; import 'web_authentication_session/main.dart'; +import 'proxy_controller.dart'; /// Implementation of [InAppWebViewPlatform] using the WebKit API. class IOSInAppWebViewPlatform extends InAppWebViewPlatform { @@ -270,4 +271,13 @@ class IOSInAppWebViewPlatform extends InAppWebViewPlatform { IOSWebAuthenticationSession createPlatformWebAuthenticationSessionStatic() { return IOSWebAuthenticationSession.static(); } + + /// Creates a new [IOSProxyController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [ProxyController] in `flutter_inappwebview` instead. + @override + PlatformProxyController createPlatformProxyController(PlatformProxyControllerCreationParams params) { + return IOSProxyController(params); + } } diff --git a/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart index e2385f9c3..f817d8501 100644 --- a/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_ios/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class IOSPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [IOSPrintJobControllerCreationParams] instance. const IOSPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [IOSPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory IOSPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class IOSPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return IOSPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -35,7 +35,6 @@ class IOSPrintJobController extends PlatformPrintJobController : IOSPrintJobControllerCreationParams .fromPlatformPrintJobControllerCreationParams(params), ) { - onComplete = params.onComplete; channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); handler = _handleMethod; diff --git a/flutter_inappwebview_ios/lib/src/proxy_controller.dart b/flutter_inappwebview_ios/lib/src/proxy_controller.dart new file mode 100644 index 000000000..a94bdef2d --- /dev/null +++ b/flutter_inappwebview_ios/lib/src/proxy_controller.dart @@ -0,0 +1,81 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [IOSProxyController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformProxyControllerCreationParams] for +/// more information. +@immutable +class IOSProxyControllerCreationParams + extends PlatformProxyControllerCreationParams { + /// Creates a new [IOSProxyControllerCreationParams] instance. + const IOSProxyControllerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformProxyControllerCreationParams params, + ) : super(); + + /// Creates a [IOSProxyControllerCreationParams] instance based on [PlatformProxyControllerCreationParams]. + factory IOSProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + PlatformProxyControllerCreationParams params) { + return IOSProxyControllerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformProxyController} +class IOSProxyController extends PlatformProxyController + with ChannelController { + /// Creates a new [IOSProxyController]. + IOSProxyController(PlatformProxyControllerCreationParams params) + : super.implementation( + params is IOSProxyControllerCreationParams + ? params + : IOSProxyControllerCreationParams + .fromPlatformProxyControllerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller'); + handler = handleMethod; + initMethodCallHandler(); + } + + static IOSProxyController? _instance; + + ///Gets the [IOSProxyController] shared instance. + static IOSProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static IOSProxyController _init() { + _instance = IOSProxyController(IOSProxyControllerCreationParams( + const PlatformProxyControllerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future setProxyOverride({required ProxySettings settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings.toMap()); + await channel?.invokeMethod('setProxyOverride', args); + } + + @override + Future clearProxyOverride() async { + Map args = {}; + await channel?.invokeMethod('clearProxyOverride', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalProxyController on IOSProxyController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart index b16b3b75a..62bf40117 100644 --- a/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_ios/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class IOSWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_ios/pubspec.yaml b/flutter_inappwebview_ios/pubspec.yaml index 06afb905a..6cda6880a 100644 --- a/flutter_inappwebview_ios/pubspec.yaml +++ b/flutter_inappwebview_ios/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_ios description: iOS implementation of the flutter_inappwebview plugin. -version: 1.1.2 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_ios issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,7 +20,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.3.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_macos/CHANGELOG.md b/flutter_inappwebview_macos/CHANGELOG.md index 7bd1ae744..825781e37 100644 --- a/flutter_inappwebview_macos/CHANGELOG.md +++ b/flutter_inappwebview_macos/CHANGELOG.md @@ -1,3 +1,27 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `PlatformProxyController` class +- Fixed internal javascript callback handlers when the WebView has windowId not null +- Fixed crash of unhandled `onPrintRequest` WebView event +- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Implemented `alpha` property of `InAppWebViewSettings` + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Implemented `requestFocus`, `clearFocus` WebView methods +- Updated ConsoleLogJS internal PluginScript to main-frame only as using it on non-main frames could cause issues such as [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738) +- Added support for `UserScript.allowedOriginRules` parameter +- Moved `WKUserContentController` initialization on `preWKWebViewConfiguration` to fix possible `undefined is not an object (evaluating 'window.webkit.messageHandlers')` javascript error +- Merged "change priority of DispatchQueue" [#2322](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2322) (thanks to [nnnlog](https://github.com/nnnlog)) +- Implemented workaround for "[macOS] Copy Shortcut does not work if TextField outside of WebView has focus" [#2380](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380) + ## 1.1.2 - Updated flutter_inappwebview_platform_interface version to ^1.3.0 diff --git a/flutter_inappwebview_macos/example/pubspec.lock b/flutter_inappwebview_macos/example/pubspec.lock index e689a7662..85b83c9fc 100644 --- a/flutter_inappwebview_macos/example/pubspec.lock +++ b/flutter_inappwebview_macos/example/pubspec.lock @@ -79,25 +79,24 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_macos: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.1.2" + version: "1.2.0-beta.3" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "6862f4e08aa8f6136762e022c9c1edafb18c1dc3beb03052f2f3f2a48605a182" - url: "https://pub.dev" - source: hosted - version: "1.3.0" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.2" flutter_lints: dependency: "direct dev" description: diff --git a/flutter_inappwebview_macos/lib/src/cookie_manager.dart b/flutter_inappwebview_macos/lib/src/cookie_manager.dart index bf9d5a9bc..6078cb526 100755 --- a/flutter_inappwebview_macos/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_macos/lib/src/cookie_manager.dart @@ -407,13 +407,11 @@ class MacOSCookieManager extends PlatformCookieManager with ChannelController { Future _getCookieExpirationDate(int expiresDate) async { var platformUtil = PlatformUtil.instance(); var dateTime = DateTime.fromMillisecondsSinceEpoch(expiresDate).toUtc(); - return !kIsWeb - ? await platformUtil.formatDate( - date: dateTime, - format: 'EEE, dd MMM yyyy hh:mm:ss z', - locale: 'en_US', - timezone: 'GMT') - : await platformUtil.getWebCookieExpirationDate(date: dateTime); + return await platformUtil.formatDate( + date: dateTime, + format: 'EEE, dd MMM yyyy HH:mm:ss z', + locale: 'en_US', + timezone: 'GMT'); } Future _shouldUseJavascript() async { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart index 23055add4..eb4911ef7 100644 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/headless_in_app_webview.dart @@ -31,8 +31,10 @@ class MacOSHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -149,6 +151,7 @@ class MacOSHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -353,13 +356,24 @@ class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart index f2800a949..98f8b15bc 100755 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; +import 'headless_in_app_webview.dart'; import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. @@ -34,8 +34,10 @@ class MacOSInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -158,6 +160,7 @@ class MacOSInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -356,15 +359,24 @@ class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart index 38ab1bc76..428dbe9dc 100644 --- a/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_macos/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,20 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; - import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [MacOSInAppWebViewController]. /// @@ -66,8 +47,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; // List of properties to be saved and restored for keep alive feature - Map _javaScriptHandlersMap = - HashMap(); + Map _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -209,6 +189,13 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController } Future _handleMethod(MethodCall call) async { + if (call.method == "_onMouseDown") { + // Workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380 + // TODO: remove when Flutter fixes this + FocusManager.instance.primaryFocus?.unfocus(); + return; + } + if (PlatformInAppWebViewController.debugLoggingSettings.enabled && call.method != "onCallJsHandler") { _debugLog(call.method, call.arguments); @@ -360,11 +347,11 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -372,20 +359,25 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -1405,17 +1397,22 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1437,7 +1434,8 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1454,43 +1452,46 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxProgress(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1516,7 +1517,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1524,7 +1525,7 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1536,7 +1537,19 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + var jsHandlerResult = null; + if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerCallback) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerCallback)(handlerData.args); + } else if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerFunction) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerFunction)(handlerData); + } else { + jsHandlerResult = await _javaScriptHandlersMap[handlerName]!(); + } + return jsonEncode(jsHandlerResult); } catch (error, stacktrace) { developer.log(error.toString() + '\n' + stacktrace.toString(), name: 'JavaScript Handler "$handlerName"'); @@ -1976,16 +1989,14 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2204,6 +2215,23 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController return await channel?.invokeMethod('getSelectedText', args); } + @override + Future requestFocus( + {FocusDirection? direction, + InAppWebViewRect? previouslyFocusedRect}) async { + Map args = {}; + args.putIfAbsent("direction", () => direction?.toNativeValue()); + args.putIfAbsent( + "previouslyFocusedRect", () => previouslyFocusedRect?.toMap()); + return await channel?.invokeMethod('requestFocus', args); + } + + @override + Future clearFocus() async { + Map args = {}; + return await channel?.invokeMethod('clearFocus', args); + } + @override Future> getMetaTags() async { List metaTags = []; @@ -2648,6 +2676,23 @@ class MacOSInAppWebViewController extends PlatformInAppWebViewController await _staticChannel.invokeMethod('clearAllCache', args); } + @override + Future setJavaScriptBridgeName(String bridgeName) async { + assert(RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName), + 'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.'); + Map args = {}; + args.putIfAbsent('bridgeName', () => bridgeName); + await _staticChannel.invokeMethod('setJavaScriptBridgeName', args); + } + + @override + Future getJavaScriptBridgeName() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getJavaScriptBridgeName', args) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart index a4acb28c7..d79a74196 100644 --- a/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_macos/lib/src/inappwebview_platform.dart @@ -9,6 +9,7 @@ import 'print_job/main.dart'; import 'web_message/main.dart'; import 'web_storage/main.dart'; import 'web_authentication_session/main.dart'; +import 'proxy_controller.dart'; /// Implementation of [InAppWebViewPlatform] using the WebKit API. class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { @@ -237,4 +238,13 @@ class MacOSInAppWebViewPlatform extends InAppWebViewPlatform { MacOSWebAuthenticationSession createPlatformWebAuthenticationSessionStatic() { return MacOSWebAuthenticationSession.static(); } + + /// Creates a new [MacOSProxyController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [ProxyController] in `flutter_inappwebview` instead. + @override + PlatformProxyController createPlatformProxyController(PlatformProxyControllerCreationParams params) { + return MacOSProxyController(params); + } } diff --git a/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart index be3136d6f..adb6df7e7 100644 --- a/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_macos/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class MacOSPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [MacOSPrintJobControllerCreationParams] instance. const MacOSPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [MacOSPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory MacOSPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class MacOSPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return MacOSPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -35,7 +35,6 @@ class MacOSPrintJobController extends PlatformPrintJobController : MacOSPrintJobControllerCreationParams .fromPlatformPrintJobControllerCreationParams(params), ) { - onComplete = params.onComplete; channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); handler = _handleMethod; diff --git a/flutter_inappwebview_macos/lib/src/proxy_controller.dart b/flutter_inappwebview_macos/lib/src/proxy_controller.dart new file mode 100644 index 000000000..6f0382b90 --- /dev/null +++ b/flutter_inappwebview_macos/lib/src/proxy_controller.dart @@ -0,0 +1,81 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +/// Object specifying creation parameters for creating a [MacOSProxyController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformProxyControllerCreationParams] for +/// more information. +@immutable +class MacOSProxyControllerCreationParams + extends PlatformProxyControllerCreationParams { + /// Creates a new [MacOSProxyControllerCreationParams] instance. + const MacOSProxyControllerCreationParams( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformProxyControllerCreationParams params, + ) : super(); + + /// Creates a [MacOSProxyControllerCreationParams] instance based on [PlatformProxyControllerCreationParams]. + factory MacOSProxyControllerCreationParams.fromPlatformProxyControllerCreationParams( + PlatformProxyControllerCreationParams params) { + return MacOSProxyControllerCreationParams(params); + } +} + +///{@macro flutter_inappwebview_platform_interface.PlatformProxyController} +class MacOSProxyController extends PlatformProxyController + with ChannelController { + /// Creates a new [MacOSProxyController]. + MacOSProxyController(PlatformProxyControllerCreationParams params) + : super.implementation( + params is MacOSProxyControllerCreationParams + ? params + : MacOSProxyControllerCreationParams + .fromPlatformProxyControllerCreationParams(params), + ) { + channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller'); + handler = handleMethod; + initMethodCallHandler(); + } + + static MacOSProxyController? _instance; + + ///Gets the [MacOSProxyController] shared instance. + static MacOSProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static MacOSProxyController _init() { + _instance = MacOSProxyController(MacOSProxyControllerCreationParams( + const PlatformProxyControllerCreationParams())); + return _instance!; + } + + Future _handleMethod(MethodCall call) async {} + + @override + Future setProxyOverride({required ProxySettings settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings.toMap()); + await channel?.invokeMethod('setProxyOverride', args); + } + + @override + Future clearProxyOverride() async { + Map args = {}; + await channel?.invokeMethod('clearProxyOverride', args); + } + + @override + void dispose() { + // empty + } +} + +extension InternalProxyController on MacOSProxyController { + get handleMethod => _handleMethod; +} diff --git a/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart index c0682298b..1618486f5 100644 --- a/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_macos/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class MacOSWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift b/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift index c11ac05b1..4a7a45df7 100755 --- a/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift +++ b/flutter_inappwebview_macos/macos/Classes/CredentialDatabase.swift @@ -14,7 +14,7 @@ public class CredentialDatabase: ChannelDelegate { static var credentialStore = URLCredentialStorage.shared init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift b/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift index 68bcae597..f6e5492a8 100644 --- a/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift +++ b/flutter_inappwebview_macos/macos/Classes/FindInteraction/FindInteractionController.swift @@ -24,11 +24,9 @@ public class FindInteractionController: NSObject, Disposable { self.plugin = plugin self.webView = webView self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) } public func prepare() { @@ -61,7 +59,7 @@ public class FindInteractionController: NSObject, Disposable { } if find != "" { - let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');" + let startSearch = "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync('\(find)');" webView.evaluateJavaScript(startSearch, completionHandler: completionHandler) } } @@ -73,7 +71,7 @@ public class FindInteractionController: NSObject, Disposable { } return } - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler) } public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) { @@ -83,7 +81,7 @@ public class FindInteractionController: NSObject, Disposable { } return } - webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler) + webView.evaluateJavaScript("window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches();", completionHandler: completionHandler) } public func dispose() { diff --git a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index b95222fb1..9f5b9524f 100644 --- a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -20,7 +20,7 @@ public class HeadlessInAppWebView: Disposable { self.flutterWebView = flutterWebView self.plugin = plugin let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger) + binaryMessenger: plugin.registrar.messenger) self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel) } diff --git a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 29bed518c..4dcd1b536 100644 --- a/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -19,7 +19,7 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift index 9f3d5c53a..68745e207 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserManager.swift @@ -19,7 +19,7 @@ public class InAppBrowserManager: ChannelDelegate { var plugin: InAppWebViewFlutterPlugin? init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift index 48b16e936..890f154e3 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -33,11 +33,11 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega var isHidden = false public override func loadView() { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger) + let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: plugin.registrar.messenger) channelDelegate = InAppBrowserChannelDelegate(channel: channel) var userScripts: [UserScript] = [] diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift index 2caa19c6e..04895703f 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift @@ -39,11 +39,9 @@ public class FlutterWebViewController: NSView, Disposable { webView = webViewTransport.webView webView!.id = viewId webView!.plugin = plugin - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) - } + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: plugin.registrar.messenger) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) webView!.frame = self.bounds webView!.initialUserScripts = userScripts } else { diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift index 5e73f981c..425ce3551 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift @@ -7,7 +7,7 @@ import FlutterMacOS import Foundation -import WebKit +@preconcurrency import WebKit public class InAppWebView: WKWebView, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, @@ -50,6 +50,11 @@ public class InAppWebView: WKWebView, WKUIDelegate, fileprivate var interceptOnlyAsyncAjaxRequestsPluginScript: PluginScript? + private var exceptedBridgeSecret = NSUUID().uuidString + private var javaScriptBridgeEnabled = true + + public override var acceptsFirstResponder: Bool { return true } + init(id: Any?, plugin: InAppWebViewFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) @@ -118,6 +123,15 @@ public class InAppWebView: WKWebView, WKUIDelegate, // } if let settings = settings { + if let viewAlpha = settings.alpha { + alphaValue = CGFloat(viewAlpha) + } + + javaScriptBridgeEnabled = settings.javaScriptBridgeEnabled + if let javaScriptBridgeOriginAllowList = settings.javaScriptBridgeOriginAllowList, javaScriptBridgeOriginAllowList.isEmpty { + // an empty list means that the JavaScript Bridge is not allowed for any origin. + javaScriptBridgeEnabled = false + } if #available(macOS 12.0, *), settings.transparentBackground { underPageBackgroundColor = .clear @@ -193,56 +207,62 @@ public class InAppWebView: WKWebView, WKUIDelegate, // This is a limitation of the official WebKit API. return } - configuration.userContentController = WKUserContentController() configuration.userContentController.initialize() if let applePayAPIEnabled = settings?.applePayAPIEnabled, applePayAPIEnabled { return } - configuration.userContentController.addPluginScript(PROMISE_POLYFILL_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(CONSOLE_LOG_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(PRINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT) - configuration.userContentController.addPluginScript(ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT) - if let settings = settings { - interceptOnlyAsyncAjaxRequestsPluginScript = createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests) - if settings.useShouldInterceptAjaxRequest { - if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + let pluginScriptsOriginAllowList = settings?.pluginScriptsOriginAllowList + let pluginScriptsForMainFrameOnly = settings?.pluginScriptsForMainFrameOnly ?? true + + let javaScriptBridgeOriginAllowList = settings?.javaScriptBridgeOriginAllowList ?? pluginScriptsOriginAllowList + let javaScriptBridgeForMainFrameOnly = settings?.javaScriptBridgeForMainFrameOnly ?? pluginScriptsForMainFrameOnly + + configuration.userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: exceptedBridgeSecret, allowedOriginRules: javaScriptBridgeOriginAllowList, forMainFrameOnly: javaScriptBridgeForMainFrameOnly)) + configuration.userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + configuration.userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindElementsAtPointJS.FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(FindTextHighlightJS.FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OriginalViewPortMetaTagContentJS.ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + configuration.userContentController.addPluginScript(OnScrollChangedJS.ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + if let settings = settings { + interceptOnlyAsyncAjaxRequestsPluginScript = InterceptAjaxRequestJS.createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: settings.interceptOnlyAsyncAjaxRequests, + allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly) + if settings.useShouldInterceptAjaxRequest { + if let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { + configuration.userContentController.addPluginScript(interceptOnlyAsyncAjaxRequestsPluginScript) + } + configuration.userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, + forMainFrameOnly: pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: settings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: settings.useOnAjaxProgress)) + } + if settings.useShouldInterceptFetchRequest { + configuration.userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if settings.useOnLoadResource { + configuration.userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList, forMainFrameOnly: pluginScriptsForMainFrameOnly)) + } + if !settings.supportZoom { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) + } else if settings.enableViewportScale { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: pluginScriptsOriginAllowList)) } - configuration.userContentController.addPluginScript(INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useShouldInterceptFetchRequest { - configuration.userContentController.addPluginScript(INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) - } - if settings.useOnLoadResource { - configuration.userContentController.addPluginScript(ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) - } - if !settings.supportZoom { - configuration.userContentController.addPluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - } else if settings.enableViewportScale { - configuration.userContentController.addPluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) } } - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") - configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived") configuration.userContentController.addUserOnlyScripts(initialUserScripts) configuration.userContentController.sync(scriptMessageHandler: self) } public static func preWKWebViewConfiguration(settings: InAppWebViewSettings?) -> WKWebViewConfiguration { let configuration = WKWebViewConfiguration() - + // initialzie WKUserContentController here to fix possible "undefined is not an object (evaluating 'window.webkit.messageHandlers')" javascript error + configuration.userContentController = WKUserContentController() configuration.processPool = WKProcessPoolManager.sharedProcessPool if let settings = settings { @@ -354,11 +374,11 @@ public class InAppWebView: WKWebView, WKUIDelegate, if #available(macOS 11.0, *) { let contentWorlds = configuration.userContentController.getContentWorlds(with: windowId) for contentWorld in contentWorlds { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source, contentWorld: contentWorld) } } else { - let source = WINDOW_ID_INITIALIZE_JS_SOURCE.replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) + let source = WindowIdJS.WINDOW_ID_INITIALIZE_JS_SOURCE().replacingOccurrences(of: PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, with: String(windowId)) evaluateJavascript(source: source) } } @@ -547,6 +567,10 @@ public class InAppWebView: WKWebView, WKUIDelegate, } } + if newSettingsMap["alpha"] != nil, settings?.alpha != newSettings.alpha, let viewAlpha = newSettings.alpha { + alphaValue = CGFloat(viewAlpha) + } + if (newSettingsMap["incognito"] != nil && settings?.incognito != newSettings.incognito && newSettings.incognito) { configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent() } else if (newSettingsMap["cacheEnabled"] != nil && settings?.cacheEnabled != newSettings.cacheEnabled && newSettings.cacheEnabled) { @@ -566,33 +590,40 @@ public class InAppWebView: WKWebView, WKUIDelegate, if newSettingsMap["enableViewportScale"] != nil && settings?.enableViewportScale != newSettings.enableViewportScale { if !newSettings.enableViewportScale { - if configuration.userContentController.userScripts.contains(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) - evaluateJavaScript(NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(EnableViewportScaleJS.NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE()) } } else { - evaluateJavaScript(ENABLE_VIEWPORT_SCALE_JS_SOURCE) - configuration.userContentController.addUserScript(ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT) + evaluateJavaScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(EnableViewportScaleJS.ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["supportZoom"] != nil && settings?.supportZoom != newSettings.supportZoom { if newSettings.supportZoom { - if configuration.userContentController.userScripts.contains(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) { - configuration.userContentController.removePluginScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) - evaluateJavaScript(SUPPORT_ZOOM_JS_SOURCE) + if configuration.userContentController.containsPluginScript(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME) { + configuration.userContentController.removePluginScripts(with: SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, shouldAddPreviousScripts: false) + evaluateJavaScript(SupportZoomJS.SUPPORT_ZOOM_JS_SOURCE()) } } else { - evaluateJavaScript(NOT_SUPPORT_ZOOM_JS_SOURCE) - configuration.userContentController.addUserScript(NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT) + evaluateJavaScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_SOURCE) + if javaScriptBridgeEnabled { + configuration.userContentController.addPluginScript(SupportZoomJS.NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList)) + } } } if newSettingsMap["useOnLoadResource"] != nil && settings?.useOnLoadResource != newSettings.useOnLoadResource { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE, - enable: newSettings.useOnLoadResource, - pluginScript: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: OnLoadResourceJS.FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE(), + enable: newSettings.useOnLoadResource, + pluginScript: OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useOnLoadResource = false } @@ -600,28 +631,58 @@ public class InAppWebView: WKWebView, WKUIDelegate, if newSettingsMap["useShouldInterceptAjaxRequest"] != nil && settings?.useShouldInterceptAjaxRequest != newSettings.useShouldInterceptAjaxRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptAjaxRequest, - pluginScript: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptAjaxRequest, + pluginScript: InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly, + initialUseOnAjaxReadyStateChange: newSettings.useOnAjaxReadyStateChange, + initialUseOnAjaxProgress: newSettings.useOnAjaxProgress)) + } } else { newSettings.useShouldInterceptAjaxRequest = false } } + if newSettingsMap["useOnAjaxReadyStateChange"] != nil && settings?.useOnAjaxReadyStateChange != newSettings.useOnAjaxReadyStateChange { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(newSettings.useOnAjaxReadyStateChange);") + } + } else { + newSettings.useOnAjaxReadyStateChange = false + } + } + + if newSettingsMap["useOnAjaxProgress"] != nil && settings?.useOnAjaxProgress != newSettings.useOnAjaxProgress { + if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { + if javaScriptBridgeEnabled { + evaluateJavaScript("\(InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(newSettings.useOnAjaxProgress);") + } + } else { + newSettings.useOnAjaxProgress = false + } + } + if newSettingsMap["interceptOnlyAsyncAjaxRequests"] != nil && settings?.interceptOnlyAsyncAjaxRequests != newSettings.interceptOnlyAsyncAjaxRequests { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled, let interceptOnlyAsyncAjaxRequestsPluginScript = interceptOnlyAsyncAjaxRequestsPluginScript { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE, - enable: newSettings.interceptOnlyAsyncAjaxRequests, - pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptAjaxRequestJS.FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE(), + enable: newSettings.interceptOnlyAsyncAjaxRequests, + pluginScript: interceptOnlyAsyncAjaxRequestsPluginScript) + } } } if newSettingsMap["useShouldInterceptFetchRequest"] != nil && settings?.useShouldInterceptFetchRequest != newSettings.useShouldInterceptFetchRequest { if let applePayAPIEnabled = settings?.applePayAPIEnabled, !applePayAPIEnabled { - enablePluginScriptAtRuntime(flagVariable: FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE, - enable: newSettings.useShouldInterceptFetchRequest, - pluginScript: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT) + if javaScriptBridgeEnabled { + enablePluginScriptAtRuntime(flagVariable: InterceptFetchRequestJS.FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + enable: newSettings.useShouldInterceptFetchRequest, + pluginScript: InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: newSettings.pluginScriptsOriginAllowList, + forMainFrameOnly: newSettings.pluginScriptsForMainFrameOnly)) + } } else { newSettings.useShouldInterceptFetchRequest = false } @@ -968,7 +1029,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, let functionArgumentNames = functionArgumentNamesList.joined(separator: ", ") let functionArgumentValues = functionArgumentValuesList.joined(separator: ", ") - jsToInject = CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS + jsToInject = CallAsyncJavaScriptBelowIOS14WrapperJS.CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES, with: functionArgumentNames) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES, with: functionArgumentValues) .replacingOccurrences(of: PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ, with: Util.JSONStringify(value: arguments)) @@ -999,15 +1060,15 @@ public class InAppWebView: WKWebView, WKUIDelegate, scriptAttributes += " script.id = '\(scriptIdEscaped)'; " scriptAttributes += """ script.onload = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptLoaded', '\(scriptIdEscaped)'); } }; """ scriptAttributes += """ script.onerror = function() { - if (window.\(JAVASCRIPT_BRIDGE_NAME) != null) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onInjectedScriptError', '\(scriptIdEscaped)'); } }; """ @@ -1168,7 +1229,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil // cancel the download @@ -1186,7 +1247,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: response.suggestedFilename, textEncodingName: response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) } download.delegate = nil } @@ -1279,7 +1340,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, contentLength: navigationResponse.response.expectedContentLength, suggestedFilename: navigationResponse.response.suggestedFilename, textEncodingName: navigationResponse.response.textEncodingName) - channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStarting(request: downloadStartRequest) if useOnNavigationResponse == nil || !useOnNavigationResponse! { decisionHandler(.cancel) } @@ -1313,7 +1374,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, initializeWindowIdJS() InAppWebView.credentialsProposed = [] - evaluateJavaScript(PLATFORM_READY_JS_SOURCE, completionHandler: nil) + evaluateJavaScript(JavaScriptBridgeJS.PLATFORM_READY_JS_SOURCE, completionHandler: nil) channelDelegate?.onLoadStop(url: url?.absoluteString) @@ -1455,7 +1516,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, if let scheme = challenge.protectionSpace.protocol, scheme == "https" { // workaround for ProtectionSpace SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { if let sslCertificate = challenge.protectionSpace.sslCertificate { DispatchQueue.main.async { InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate @@ -1475,7 +1536,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, break case 1: // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let exceptions = SecTrustCopyExceptions(serverTrust) SecTrustSetExceptions(serverTrust, exceptions) let credential = URLCredential(trust: serverTrust) @@ -2098,167 +2159,251 @@ public class InAppWebView: WKWebView, WKUIDelegate, // } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard javaScriptBridgeEnabled else { + return + } + guard let body = message.body as? [String: Any?] else { return } - if ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"].contains(message.name) { - var messageLevel = 1 - switch (message.name) { - case "consoleLog": - messageLevel = 1 - break; - case "consoleDebug": - // on Android, console.debug is TIP - messageLevel = 0 - break; - case "consoleError": - messageLevel = 3 - break; - case "consoleInfo": - // on Android, console.info is LOG - messageLevel = 1 - break; - case "consoleWarn": - messageLevel = 2 - break; - default: - messageLevel = 1 - break; + guard let bridgeSecret = body["_bridgeSecret"] as? String, bridgeSecret == exceptedBridgeSecret else { + print("Bridge access attempt with wrong secret token, possibly from malicious code from origin \(message.frameInfo.securityOrigin)") + return + } + + var sourceOrigin: URL? = nil + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + let requestUrl = message.frameInfo.request.url + + var isOriginAllowed = false + if let javaScriptHandlersOriginAllowList = settings?.javaScriptHandlersOriginAllowList { + if let origin = sourceOrigin?.absoluteString { + for allowedOrigin in javaScriptHandlersOriginAllowList { + if origin.range(of: allowedOrigin, options: .regularExpression, range: nil, locale: nil) != nil { + isOriginAllowed = true + break + } + } + } + } else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true + } + + if !isOriginAllowed { + print("Bridge access attempt from an origin not allowed: \(message.frameInfo.securityOrigin)") + return + } + + if message.name == "callHandler" { + guard let handlerName = body["handlerName"] as? String else { + print("handlerName is null or undefined") + return } - let consoleMessage = body["message"] as? String ?? "" let _windowId = body["_windowId"] as? Int64 var webView = self if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } - webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) - } else if message.name == "callHandler", let handlerName = body["handlerName"] as? String { - if handlerName == "onPrintRequest" { - let settings = PrintJobSettings() - settings.handledByClient = true - if let printJobId = printCurrentPage(settings: settings) { - let callback = WebViewChannelDelegate.PrintRequestCallback() - callback.nonNullSuccess = { (handledByClient: Bool) in - return !handledByClient + var isInternalHandler = true + switch (handlerName) { + case "onPrintRequest": + let settings = PrintJobSettings() + settings.handledByClient = true + if let printJobId = webView.printCurrentPage(settings: settings) { + let callback = WebViewChannelDelegate.PrintRequestCallback() + callback.nonNullSuccess = { (handledByClient: Bool) in + return !handledByClient + } + callback.defaultBehaviour = { (handledByClient: Bool?) in + if let printJob = webView.plugin?.printJobManager?.jobs[printJobId] { + printJob?.disposeWhenDidRun = true + } + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + webView.channelDelegate?.onPrintRequest(url: webView.url, printJobId: printJobId, callback: callback) + } + break + case "onConsoleMessage": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first { + var messageLevel = 1 + switch (jsonData["level"] as? String) { + case "log": + messageLevel = 1 + break + case "debug": + // on Android, console.debug is TIP + messageLevel = 0 + break + case "error": + messageLevel = 3 + break + case "info": + // on Android, console.info is LOG + messageLevel = 1 + break + case "warn": + messageLevel = 2 + break + default: + messageLevel = 1 + break + } + let consoleMessage = jsonData["message"] as? String ?? "" + + webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) + } } - callback.defaultBehaviour = { [weak self] (handledByClient: Bool?) in - if let printJob = self?.plugin?.printJobManager?.jobs[printJobId] { - printJob?.disposeNoDismiss() + break + case "onFindResultReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let findResult = jsonData["findResult"] as? [String: Any], + let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, + let numberOfMatches = findResult["numberOfMatches"] as? Int, + let isDoneCounting = findResult["isDoneCounting"] as? Bool { + webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) } } - callback.error = { [weak callback] (code: String, message: String?, details: Any?) in - print(code + ", " + (message ?? "")) - callback?.defaultBehaviour(nil) + break + case "onCallAsyncJavaScriptResultBelowIOS14Received": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let resultUuid = jsonData["resultUuid"] as? String, + let result = webView.callAsyncJavaScriptBelowMacOS11Results[resultUuid] { + result([ + "value": jsonData["value"], + "error": jsonData["error"] + ]) + webView.callAsyncJavaScriptBelowMacOS11Results.removeValue(forKey: resultUuid) + } } - channelDelegate?.onPrintRequest(url: url, printJobId: printJobId, callback: callback) - } - return + break + case "onWebMessagePortMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let webMessageChannelId = jsonData["webMessageChannelId"] as? String, + let index = jsonData["index"] as? Int64 { + var webMessage: WebMessage? = nil + if let webMessageMap = jsonData["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { + webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) + } + } + } + break + case "onWebMessageListenerPostMessageReceived": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, let jsObjectName = jsonData["jsObjectName"] as? String { + var webMessage: WebMessage? = nil + if let webMessageMap = body["message"] as? [String : Any?] { + webMessage = WebMessage.fromMap(map: webMessageMap) + } + + if let webMessageListener = webView.webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { + let isMainFrame = message.frameInfo.isMainFrame + + let securityOrigin = message.frameInfo.securityOrigin + let scheme = securityOrigin.protocol + let host = securityOrigin.host + let port = securityOrigin.port + + if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { + return + } + + var sourceOrigin: URL? = nil + if !scheme.isEmpty, !host.isEmpty { + sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") + } + webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + } + } + } + break + case "onScrollChanged": + if let args = body["args"] as? String, let data = args.data(using: .utf8) { + let jsonArgs = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String: Any]] + if let jsonData = jsonArgs?.first, + let x = jsonData["x"] as? Int, + let y = jsonData["y"] as? Int { + webView.channelDelegate?.onScrollChanged(x: x, y: y) + } + } + break + default: + isInternalHandler = false + break } let _callHandlerID = body["_callHandlerID"] as? Int64 ?? 0 - let args = body["args"] as? String ?? "" - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView + if isInternalHandler { + evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; +} +""", completionHandler: nil) + return } + let args = body["args"] as? String ?? "" + let callback = WebViewChannelDelegate.CallJsHandlerCallback() - callback.defaultBehaviour = { [weak self] (response: Any?) in + callback.defaultBehaviour = { (response: Any?) in var json = "null" if let r = response as? String { json = r } - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].resolve(\(json)); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].resolve(\(json)); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } - callback.error = { [weak self] (code: String, message: String?, details: Any?) in + callback.error = { (code: String, message: String?, details: Any?) in let errorMessage = code + (message != nil ? ", " + (message ?? "") : "") print(errorMessage) - self?.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; + webView.evaluateJavaScript(""" +if(window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)] != null) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)].reject(new Error('\(errorMessage.replacingOccurrences(of: "\'", with: "\\'"))')); + delete window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())[\(_callHandlerID)]; } """, completionHandler: nil) } if let channelDelegate = webView.channelDelegate { - channelDelegate.onCallJsHandler(handlerName: handlerName, args: args, callback: callback) - } - } else if message.name == "onFindResultReceived", - let findResult = body["findResult"] as? [String: Any], - let activeMatchOrdinal = findResult["activeMatchOrdinal"] as? Int, - let numberOfMatches = findResult["numberOfMatches"] as? Int, - let isDoneCounting = findResult["isDoneCounting"] as? Bool { - - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) - } else if message.name == "onScrollChanged", - let x = body["x"] as? Int, - let y = body["y"] as? Int { - let _windowId = body["_windowId"] as? Int64 - var webView = self - if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { - webView = webViewTransport.webView - } - webView.channelDelegate?.onScrollChanged(x: x, y: y) - } else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received", - let resultUuid = body["resultUuid"] as? String, - let result = callAsyncJavaScriptBelowMacOS11Results[resultUuid] { - result([ - "value": body["value"], - "error": body["error"] - ]) - callAsyncJavaScriptBelowMacOS11Results.removeValue(forKey: resultUuid) - } else if message.name == "onWebMessagePortMessageReceived", - let webMessageChannelId = body["webMessageChannelId"] as? String, - let index = body["index"] as? Int64 { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageChannel = webMessageChannels[webMessageChannelId] { - webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) - } - } else if message.name == "onWebMessageListenerPostMessageReceived", let jsObjectName = body["jsObjectName"] as? String { - var webMessage: WebMessage? = nil - if let webMessageMap = body["message"] as? [String : Any?] { - webMessage = WebMessage.fromMap(map: webMessageMap) - } - - if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { - let isMainFrame = message.frameInfo.isMainFrame - - let securityOrigin = message.frameInfo.securityOrigin - let scheme = securityOrigin.protocol - let host = securityOrigin.host - let port = securityOrigin.port - - if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) { - return - } - - var sourceOrigin: URL? = nil - if !scheme.isEmpty, !host.isEmpty { - sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") - } - webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + let data = JavaScriptHandlerFunctionData( + args: args, isMainFrame: message.frameInfo.isMainFrame, + origin: sourceOrigin?.absoluteString ?? "", + requestUrl: requestUrl?.absoluteString ?? "" + ) + channelDelegate.onCallJsHandler(handlerName: handlerName, data: data, callback: callback) } } } @@ -2478,6 +2623,48 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } + public func clearFocus() -> Bool { + return (self.superview?.window ?? self.window)?.makeFirstResponder(nil) ?? false + } + + public func requestFocus() -> Bool { + return (self.superview?.window ?? self.window)?.makeFirstResponder(self) ?? false + } + + // Workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/2380 + // TODO: remove when Flutter fixes this + private var _isFirstResponder = true + override open func becomeFirstResponder() -> Bool { + _isFirstResponder = true + return super.becomeFirstResponder() + } + private func _fixFocus(callback: @escaping () -> Void) { + if _isFirstResponder, let channelDelegate = channelDelegate { + _isFirstResponder = false + channelDelegate._onMouseDown(callback: { [weak self] in + let _ = self?.requestFocus() + callback() + }) + } else { + callback() + } + } + override public func mouseDown(with event: NSEvent) { + _fixFocus { + super.mouseDown(with: event) + } + } + override public func rightMouseDown(with event: NSEvent) { + _fixFocus { + super.rightMouseDown(with: event) + } + } + override public func otherMouseDown(with event: NSEvent) { + _fixFocus { + super.otherMouseDown(with: event) + } + } + public func getCertificate() -> SslCertificate? { guard let scheme = url?.scheme, scheme == "https", @@ -2543,7 +2730,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } @@ -2594,6 +2781,16 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } + @available(macOS 12.0, *) + public func saveState() -> Data? { + return interactionState is NSData || interactionState is Data ? interactionState as? Data : nil + } + + @available(macOS 12.0, *) + public func restoreState(state: Data) { + interactionState = state + } + public func runWindowBeforeCreatedCallbacks() { let callbacks = windowBeforeCreatedCallbacks callbacks.forEach { (callback) in @@ -2631,9 +2828,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { interceptOnlyAsyncAjaxRequestsPluginScript = nil if windowId == nil { configuration.userContentController.removeAllPluginScriptMessageHandlers() - configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived") - configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived") configuration.userContentController.removeAllUserScripts() if #available(macOS 10.13, *) { configuration.userContentController.removeAllContentRuleLists() diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift index 1afc4fe8d..d4d369bd6 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewManager.swift @@ -20,7 +20,7 @@ public class InAppWebViewManager: ChannelDelegate { var windowAutoincrementId: Int64 = 0 init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } @@ -51,6 +51,14 @@ public class InAppWebViewManager: ChannelDelegate { clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: { result(true) }) + case "setJavaScriptBridgeName": + let bridgeName = arguments!["bridgeName"] as! String + JavaScriptBridgeJS.set_JAVASCRIPT_BRIDGE_NAME(bridgeName: bridgeName) + result(true) + break + case "getJavaScriptBridgeName": + result(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()) + break default: result(FlutterMethodNotImplemented) break diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift index 7b2b9cba5..d2785dfca 100755 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebViewSettings.swift @@ -25,6 +25,8 @@ public class InAppWebViewSettings: ISettings { var contentBlockers: [[String: [String : Any]]] = [] var minimumFontSize = 0 var useShouldInterceptAjaxRequest = false + var useOnAjaxReadyStateChange = false + var useOnAjaxProgress = false var interceptOnlyAsyncAjaxRequests = true var useShouldInterceptFetchRequest = false var incognito = false @@ -55,12 +57,26 @@ public class InAppWebViewSettings: ISettings { var isElementFullscreenEnabled = true var isInspectable = false var shouldPrintBackgrounds = false + var javaScriptHandlersOriginAllowList: [String]? = nil + var javaScriptBridgeEnabled = true + var javaScriptBridgeOriginAllowList: [String]? = nil + var javaScriptBridgeForMainFrameOnly = false + var pluginScriptsOriginAllowList: [String]? = nil + var pluginScriptsForMainFrameOnly = false + var alpha: Double? = nil override init(){ super.init() } override func parse(settings: [String: Any?]) -> InAppWebViewSettings { + var settings = settings // re-assing to be able to use removeValue + // nullable values with primitive type (Int, Double, etc.) + // must be handled here as super.parse will not work + if let alphaValue = settings["alpha"] as? Double { + alpha = alphaValue + settings.removeValue(forKey: "alpha") + } let _ = super.parse(settings: settings) if #available(macOS 10.15, *) {} else { applePayAPIEnabled = false diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index c806e0dec..76deada77 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -20,11 +20,9 @@ public class WebMessageChannel: FlutterMethodCallDelegate { self.id = id self.plugin = plugin super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.ports = [ WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self), WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self) @@ -36,7 +34,7 @@ public class WebMessageChannel: FlutterMethodCallDelegate { if let webView = self.webView { webView.evaluateJavascript(source: """ (function() { - \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"] = new MessageChannel(); })(); """) { (_) in completionHandler?(self) @@ -61,11 +59,11 @@ public class WebMessageChannel: FlutterMethodCallDelegate { ports.removeAll() webView?.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; if (webMessageChannel != null) { webMessageChannel.port1.close(); webMessageChannel.port2.close(); - delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + delete \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(id)"]; } })(); """) diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift index 270f01d3d..782861b82 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -24,11 +24,9 @@ public class WebMessageListener: FlutterMethodCallDelegate { self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) - } + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) } public func assertOriginRulesValid() throws { @@ -97,23 +95,30 @@ public class WebMessageListener: FlutterMethodCallDelegate { }.joined(separator: ", ") let source = """ (function() { + \(WebMessageListener.isOriginAllowedJs) + var allowedOriginRules = [\(allowedOriginRulesString)]; var isPageBlank = window.location.href === "about:blank"; var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null; var host = !isPageBlank ? window.location.hostname : null; var port = !isPageBlank ? window.location.port : null; - if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) { + if (_isOriginAllowed(allowedOriginRules, scheme, host, port)) { window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)'); } })(); """ + + let allowedOriginRules = webView.settings?.pluginScriptsOriginAllowList + let forMainFrameOnly = webView.settings?.pluginScriptsForMainFrameOnly ?? true + webView.configuration.userContentController.addPluginScript(PluginScript( groupName: "WebMessageListener-" + id + "-" + jsObjectName, source: source, injectionTime: .atDocumentStart, - forMainFrameOnly: false, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, requiredInAllContentWorlds: false, - messageHandlerNames: ["onWebMessageListenerPostMessageReceived"] + messageHandlerNames: [] )) webView.configuration.userContentController.sync(scriptMessageHandler: webView) } @@ -178,6 +183,87 @@ public class WebMessageListener: FlutterMethodCallDelegate { } return false } + + private static let isOriginAllowedJs = """ + var _normalizeIPv6 = function(ip_string) { + // replace ipv4 address if any + var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); + if (ipv4) { + ip_string = ipv4[1]; + ipv4 = ipv4[2].match(/[0-9]+/g); + for (var i = 0;i < 4;i ++) { + var byte = parseInt(ipv4[i],10); + ipv4[i] = ("0" + byte.toString(16)).substr(-2); + } + ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; + } + + // take care of leading and trailing :: + ip_string = ip_string.replace(/^:|:$/g, ''); + + var ipv6 = ip_string.split(':'); + + for (var i = 0; i < ipv6.length; i ++) { + var hex = ipv6[i]; + if (hex != "") { + // normalize leading zeros + ipv6[i] = ("0000" + hex).substr(-4); + } + else { + // normalize grouped zeros :: + hex = []; + for (var j = ipv6.length; j <= 8; j ++) { + hex.push('0000'); + } + ipv6[i] = hex.join(':'); + } + } + + return ipv6.join(':'); + }; + + var _isOriginAllowed = function(allowedOriginRules, scheme, host, port) { + for (var rule of allowedOriginRules) { + if (rule === "*") { + return true; + } + if (scheme == null || scheme === "") { + continue; + } + if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { + continue; + } + var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; + var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; + var IPv6 = null; + if (rule.host != null && rule.host[0] === "[") { + try { + IPv6 = _normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); + } catch {} + } + var hostIPv6 = null; + try { + hostIPv6 = _normalizeIPv6(host); + } catch {} + + var schemeAllowed = scheme == rule.scheme; + + var hostAllowed = rule.host == null || + rule.host === "" || + host === rule.host || + (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || + (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); + + var portAllowed = rulePort === currentPort; + + if (schemeAllowed && hostAllowed && portAllowed) { + return true; + } + } + return false; + }; + """ + public func dispose() { channelDelegate?.dispose() diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift index 9eb906e57..27389babb 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -370,6 +370,12 @@ public class WebViewChannelDelegate: ChannelDelegate { result(nil) } break + case .clearFocus: + result(webView?.clearFocus()) + break + case .requestFocus: + result(webView?.requestFocus()) + break case .getCertificate: result(webView?.getCertificate()?.toMap()) break @@ -658,6 +664,23 @@ public class WebViewChannelDelegate: ChannelDelegate { } else { result(false) } + break + case .saveState: + if let webView = webView, #available(macOS 12.0, *) { + result(webView.saveState()) + } else { + result(nil) + } + break + case .restoreState: + if let webView = webView, #available(macOS 12.0, *) { + let state = arguments!["state"] as! FlutterStandardTypedData + webView.restoreState(state: state.data) + result(true) + } else { + result(false) + } + break } } @@ -680,8 +703,8 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onScrollChanged", arguments: arguments) } - public func onDownloadStartRequest(request: DownloadStartRequest) { - channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) + public func onDownloadStarting(request: DownloadStartRequest) { + channel?.invokeMethod("onDownloadStarting", arguments: request.toMap()) } public func onCreateContextMenu(hitTestResult: HitTestResult) { @@ -928,7 +951,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -960,7 +983,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -992,7 +1015,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1046,14 +1069,14 @@ public class WebViewChannelDelegate: ChannelDelegate { } } - public func onCallJsHandler(handlerName: String, args: String, callback: CallJsHandlerCallback) { + public func onCallJsHandler(handlerName: String, data: JavaScriptHandlerFunctionData, callback: CallJsHandlerCallback) { if channel == nil { callback.defaultBehaviour(nil) return } let arguments: [String: Any?] = [ "handlerName": handlerName, - "args": args + "data": data.toMap() ] channel?.invokeMethod("onCallJsHandler", arguments: arguments, callback: callback) } @@ -1105,7 +1128,7 @@ public class WebViewChannelDelegate: ChannelDelegate { } // workaround for ProtectionSpace.toMap() SSL Certificate // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1678 - DispatchQueue.global(qos: .background).async { + DispatchQueue.global().async { let arguments = challenge.toMap() DispatchQueue.main.async { [weak self] in if self?.channel == nil { @@ -1166,6 +1189,16 @@ public class WebViewChannelDelegate: ChannelDelegate { channel?.invokeMethod("onPrintRequest", arguments: arguments, callback: callback) } + internal func _onMouseDown(callback: @escaping () -> Void) { + if channel == nil { + return + } + let arguments: [String:Any] = [:]; + channel?.invokeMethod("_onMouseDown", arguments: arguments) {(result) -> Void in + callback() + } + } + public override func dispose() { super.dispose() webView = nil diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift index 4810bae3b..b432dc608 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebView/WebViewChannelDelegateMethods.swift @@ -58,6 +58,8 @@ public enum WebViewChannelDelegateMethods: String { case getSelectedText = "getSelectedText" case getScrollX = "getScrollX" case getScrollY = "getScrollY" + case clearFocus = "clearFocus" + case requestFocus = "requestFocus" case getCertificate = "getCertificate" case addUserScript = "addUserScript" case removeUserScript = "removeUserScript" @@ -84,4 +86,6 @@ public enum WebViewChannelDelegateMethods: String { case getMicrophoneCaptureState = "getMicrophoneCaptureState" case setMicrophoneCaptureState = "setMicrophoneCaptureState" case loadSimulatedRequest = "loadSimulatedRequest" + case saveState = "saveState" + case restoreState = "restoreState" } diff --git a/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift b/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift index 530efdd79..57f6dddaa 100644 --- a/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift +++ b/flutter_inappwebview_macos/macos/Classes/InAppWebViewFlutterPlugin.swift @@ -25,7 +25,7 @@ import SafariServices public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { - var registrar: FlutterPluginRegistrar? + var registrar: FlutterPluginRegistrar var platformUtil: PlatformUtil? var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? @@ -35,13 +35,14 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { var headlessInAppWebViewManager: HeadlessInAppWebViewManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? var printJobManager: PrintJobManager? + var proxyManager: Any? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] public init(with registrar: FlutterPluginRegistrar) { - super.init() self.registrar = registrar + super.init() registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) @@ -55,6 +56,9 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { myWebStorageManager = MyWebStorageManager(plugin: self) webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) printJobManager = PrintJobManager(plugin: self) + if #available(macOS 14.0, *) { + proxyManager = ProxyManager(plugin: self) + } } public static func register(with registrar: FlutterPluginRegistrar) { @@ -73,7 +77,7 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { credentialDatabase?.dispose() credentialDatabase = nil if #available(macOS 10.13, *) { - (myCookieManager as! MyCookieManager?)?.dispose() + (myCookieManager as? MyCookieManager)?.dispose() myCookieManager = nil } myWebStorageManager?.dispose() @@ -82,5 +86,9 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { webAuthenticationSessionManager = nil printJobManager?.dispose() printJobManager = nil + if #available(macOS 14.0, *) { + (proxyManager as? ProxyManager)?.dispose() + proxyManager = nil + } } } diff --git a/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift b/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift index 86e62d7ef..856ac1a25 100755 --- a/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/MyCookieManager.swift @@ -16,7 +16,7 @@ public class MyCookieManager: ChannelDelegate { static var httpCookieStore = WKWebsiteDataStore.default().httpCookieStore init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift b/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift index a20b46e6a..c77840bbb 100755 --- a/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/MyWebStorageManager.swift @@ -15,7 +15,7 @@ public class MyWebStorageManager: ChannelDelegate { static var websiteDataStore = WKWebsiteDataStore.default() init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift b/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift index c9859d7d8..20f96e9cf 100644 --- a/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift +++ b/flutter_inappwebview_macos/macos/Classes/PlatformUtil.swift @@ -13,7 +13,7 @@ public class PlatformUtil: ChannelDelegate { var plugin: InAppWebViewFlutterPlugin? init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } @@ -51,6 +51,7 @@ public class PlatformUtil: ChannelDelegate { static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String { let formatter = DateFormatter() + formatter.locale = locale formatter.dateFormat = format formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift index 5622691a1..f1f83db00 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/CallAsyncJavaScriptBelowIOS14WrapperJS.swift @@ -7,15 +7,28 @@ import Foundation -let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """ -(function(obj) { - (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { - \(PluginScriptsUtil.VAR_FUNCTION_BODY) - })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }).catch(function(error) { - window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'}); - }); - return null; -})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); -""" +public class CallAsyncJavaScriptBelowIOS14WrapperJS { + + public static func CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS() -> String { + return """ + (function(obj) { + (async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) { + \(PluginScriptsUtil.VAR_FUNCTION_BODY) + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': value, + 'error': null, + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }).catch(function(error) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onCallAsyncJavaScriptResultBelowIOS14Received', { + 'value': null, + 'error': error + '', + 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)' + }); + }); + return null; + })(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ)); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift index d244d8993..5636ea62c 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/ConsoleLogJS.swift @@ -7,46 +7,59 @@ import Foundation -let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" - -let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript( - groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: CONSOLE_LOG_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"]) - -// the message needs to be concatenated with '' in order to have the same behavior like on Android -let CONSOLE_LOG_JS_SOURCE = """ -(function(console) { - - function _callHandler(oldLog, args) { - var message = ''; - for (var i in args) { - try { - message += message === '' ? args[i] : ' ' + args[i]; - } catch(ignored) {} - } - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId}); +public class ConsoleLogJS { + + public static let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame. + // Using it also on non-main frames could cause issues + // such as https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738 + public static func CONSOLE_LOG_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: CONSOLE_LOG_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) } - - var oldLogs = { - 'consoleLog': console.log, - 'consoleDebug': console.debug, - 'consoleError': console.error, - 'consoleInfo': console.info, - 'consoleWarn': console.warn - }; - - for (var k in oldLogs) { - (function(oldLog) { - console[oldLog.replace('console', '').toLowerCase()] = function() { - oldLogs[oldLog].apply(null, arguments); - _callHandler(oldLog, arguments); + + // the message needs to be concatenated with '' in order to have the same behavior like on Android + public static func CONSOLE_LOG_JS_SOURCE() -> String { + return """ + (function(console) { + + function _callHandler(logLevel, args) { + var message = ''; + for (var i in args) { + try { + message += message === '' ? args[i] : ' ' + args[i]; + } catch(_) {} + } + try { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onConsoleMessage', {'level': logLevel, 'message': message}) + } catch(_) {} + } + + var oldLogs = { + 'consoleLog': console.log, + 'consoleDebug': console.debug, + 'consoleError': console.error, + 'consoleInfo': console.info, + 'consoleWarn': console.warn + }; + + for (var k in oldLogs) { + (function(oldLog) { + var logLevel = oldLog.replace('console', '').toLowerCase(); + console[logLevel] = function() { + oldLogs[oldLog].apply(null, arguments); + _callHandler(logLevel, arguments); + } + })(k); } - })(k); + })(window.console); + """ } -})(window.console); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift index 593ba00eb..efa07583a 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/EnableViewportScaleJS.swift @@ -7,30 +7,39 @@ import Foundation -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" - -let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', 'width=device-width'); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" - -let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ -(function() { - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); - document.getElementsByTagName('head')[0].appendChild(meta); -})() -""" +public class EnableViewportScaleJS { + + public static let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ENABLE_VIEWPORT_SCALE_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift index b3c22282e..26220f8a5 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindElementsAtPointJS.swift @@ -7,67 +7,75 @@ import Foundation -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" - -let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_ELEMENTS_AT_POINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -/** - https://developer.android.com/reference/android/webkit/WebView.HitTestResult - */ -let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) { - var hitTestResultType = { - UNKNOWN_TYPE: 0, - PHONE_TYPE: 2, - GEO_TYPE: 3, - EMAIL_TYPE: 4, - IMAGE_TYPE: 5, - SRC_ANCHOR_TYPE: 7, - SRC_IMAGE_ANCHOR_TYPE: 8, - EDIT_TEXT_TYPE: 9 - }; - var element = document.elementFromPoint(x, y); - var data = { - type: 0, - extra: null - }; - while (element) { - if (element.tagName === 'IMG' && element.src) { - if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { - data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; - } else { - data.type = hitTestResultType.IMAGE_TYPE; - } - data.extra = element.src; - break; - } else if (element.tagName === 'A' && element.href) { - if (element.href.indexOf('mailto:') === 0) { - data.type = hitTestResultType.EMAIL_TYPE; - data.extra = element.href.replace('mailto:', ''); - } else if (element.href.indexOf('tel:') === 0) { - data.type = hitTestResultType.PHONE_TYPE; - data.extra = element.href.replace('tel:', ''); - } else if (element.href.indexOf('geo:') === 0) { - data.type = hitTestResultType.GEO_TYPE; - data.extra = element.href.replace('geo:', ''); - } else { - data.type = hitTestResultType.SRC_ANCHOR_TYPE; - data.extra = element.href; +public class FindElementsAtPointJS { + public static let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_ELEMENTS_AT_POINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + /** + https://developer.android.com/reference/android/webkit/WebView.HitTestResult + */ + public static func FIND_ELEMENTS_AT_POINT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findElementsAtPoint = function(x, y) { + var hitTestResultType = { + UNKNOWN_TYPE: 0, + PHONE_TYPE: 2, + GEO_TYPE: 3, + EMAIL_TYPE: 4, + IMAGE_TYPE: 5, + SRC_ANCHOR_TYPE: 7, + SRC_IMAGE_ANCHOR_TYPE: 8, + EDIT_TEXT_TYPE: 9 + }; + var element = document.elementFromPoint(x, y); + var data = { + type: 0, + extra: null + }; + while (element) { + if (element.tagName === 'IMG' && element.src) { + if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) { + data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE; + } else { + data.type = hitTestResultType.IMAGE_TYPE; + } + data.extra = element.src; + break; + } else if (element.tagName === 'A' && element.href) { + if (element.href.indexOf('mailto:') === 0) { + data.type = hitTestResultType.EMAIL_TYPE; + data.extra = element.href.replace('mailto:', ''); + } else if (element.href.indexOf('tel:') === 0) { + data.type = hitTestResultType.PHONE_TYPE; + data.extra = element.href.replace('tel:', ''); + } else if (element.href.indexOf('geo:') === 0) { + data.type = hitTestResultType.GEO_TYPE; + data.extra = element.href.replace('geo:', ''); + } else { + data.type = hitTestResultType.SRC_ANCHOR_TYPE; + data.extra = element.href; + } + break; + } else if ( + (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || + element.tagName === 'TEXTAREA') { + data.type = hitTestResultType.EDIT_TEXT_TYPE + } + element = element.parentNode; } - break; - } else if ( - (element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) || - element.tagName === 'TEXTAREA') { - data.type = hitTestResultType.EDIT_TEXT_TYPE + return data; } - element = element.parentNode; + """ } - return data; } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift index f0db61452..5006f04b7 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/FindTextHighlightJS.swift @@ -7,181 +7,180 @@ import Foundation -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" -let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount" -let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight" -let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting" - -let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: FIND_TEXT_HIGHLIGHT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onFindResultReceived"]) - -let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """ -\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; -\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) { - if (element) { - if (element.nodeType == 3) { - // Text node - - var elementTmp = element; - while (true) { - var value = elementTmp.nodeValue; // Search for keyword in text node - var idx = value.toLowerCase().indexOf(keyword); - - if (idx < 0) break; - - var span = document.createElement("span"); - var text = document.createTextNode(value.substr(idx, keyword.length)); - span.appendChild(text); - - span.setAttribute( - "id", - "\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ); - span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00"; - span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); - - text = document.createTextNode(value.substr(idx + keyword.length)); - element.deleteData(idx, value.length - idx); - - var next = element.nextSibling; - element.parentNode.insertBefore(span, next); - element.parentNode.insertBefore(text, next); - element = text; - - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++; - elementTmp = document.createTextNode( - value.substr(idx + keyword.length) - ); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId +public class FindTextHighlightJS { + public static let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT" + public static func FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._searchResultCount" + } + public static func FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._currentHighlight" + } + public static func FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._isDoneCounting" + } + + // This plugin is only for main frame + public static func FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: FIND_TEXT_HIGHLIGHT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func FIND_TEXT_HIGHLIGHT_JS_SOURCE() -> String { + return """ + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement = function(element, keyword) { + if (element) { + if (element.nodeType == 3) { + // Text node + + var elementTmp = element; + while (true) { + var value = elementTmp.nodeValue; // Search for keyword in text node + var idx = value.toLowerCase().indexOf(keyword); + + if (idx < 0) break; + + var span = document.createElement("span"); + var text = document.createTextNode(value.substr(idx, keyword.length)); + span.appendChild(text); + + span.setAttribute( + "id", + "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ); + span.setAttribute("class", "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) == 0 ? "#FF9732" : "#FFFF00"; + span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;"); + + text = document.createTextNode(value.substr(idx + keyword.length)); + element.deleteData(idx, value.length - idx); + + var next = element.nextSibling; + element.parentNode.insertBefore(span, next); + element.parentNode.insertBefore(text, next); + element = text; + + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE())++; + elementTmp = document.createTextNode( + value.substr(idx + keyword.length) + ); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + } else if (element.nodeType == 1) { + // Element node + if ( + element.style.display != "none" && + element.nodeName.toLowerCase() != "select" + ) { + for (var i = element.childNodes.length - 1; i >= 0; i--) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement( + element.childNodes[element.childNodes.length - 1 - i], + keyword + ); + } + } } - ); - } - } else if (element.nodeType == 1) { - // Element node - if ( - element.style.display != "none" && - element.nodeName.toLowerCase() != "select" - ) { - for (var i = element.childNodes.length - 1; i >= 0; i--) { - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement( - element.childNodes[element.childNodes.length - 1 - i], - keyword - ); + } } - } - } - } -} - -// the main entry point to start the search -window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) { - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches(); - window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase()); - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true; - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId - } - ); -} - -// helper function, recursively removes the highlights in elements and their children -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) { - if (element) { - if (element.nodeType == 1) { - if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") { - var text = element.removeChild(element.firstChild); - element.parentNode.insertBefore(text, element); - element.parentNode.removeChild(element); - return true; - } else { - var normalize = false; - for (var i = element.childNodes.length - 1; i >= 0; i--) { - if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) { - normalize = true; + + // the main entry point to start the search + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsync = function(keyword) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches(); + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findAllAsyncForElement(document.body, keyword.toLowerCase()); + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = true; + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } + + // helper function, recursively removes the highlights in elements and their children + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement = function(element) { + if (element) { + if (element.nodeType == 1) { + if (element.getAttribute("class") == "\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight") { + var text = element.removeChild(element.firstChild); + element.parentNode.insertBefore(text, element); + element.parentNode.removeChild(element); + return true; + } else { + var normalize = false; + for (var i = element.childNodes.length - 1; i >= 0; i--) { + if (window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(element.childNodes[i])) { + normalize = true; + } + } + if (normalize) { + element.normalize(); + } + } + } } + return false; } - if (normalize) { - element.normalize(); + + // the main entry point to remove the highlights + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatches = function() { + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = 0; + \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) = false; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._clearMatchesForElement(document.body); } - } - } - } - return false; -} - -// the main entry point to remove the highlights -window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() { - \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0; - \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false; - window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) { - if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return; - - var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1); - idx = - idx < 0 - ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1 - : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - ? 0 - : idx; - \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx; - - var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx); - if (scrollTo) { - var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight"); - for (var i = 0; i < highlights.length; i++) { - var span = highlights[i]; - span.style.backgroundColor = "#FFFF00"; - } - scrollTo.style.backgroundColor = "#FF9732"; - - scrollTo.scrollIntoView({ - behavior: "auto", - block: "center" - }); - - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - - window.webkit.messageHandlers["onFindResultReceived"].postMessage( - { - 'findResult': { - 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE), - 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE), - 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) - }, - '_windowId': _windowId + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._findNext = function(forward) { + if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) <= 0) return; + + var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) + (forward ? +1 : -1); + idx = + idx < 0 + ? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) - 1 + : idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()) + ? 0 + : idx; + \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()) = idx; + + var scrollTo = document.getElementById("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_SEARCH_WORD_" + idx); + if (scrollTo) { + var highlights = document.getElementsByClassName("\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_Highlight"); + for (var i = 0; i < highlights.length; i++) { + var span = highlights[i]; + span.style.backgroundColor = "#FFFF00"; + } + scrollTo.style.backgroundColor = "#FF9732"; + + scrollTo.scrollIntoView({ + behavior: "auto", + block: "center" + }); + + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onFindResultReceived', { + 'findResult': { + 'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE()), + 'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE()), + 'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE()) + } + }); + } } - ); - } + """ + } } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift index 4a68e9b6b..e8ca40c76 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift @@ -7,262 +7,288 @@ import Foundation -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest" - - -let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests"; - -let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_AJAX_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript { - return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);", - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: [] - ); -} - -let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true; -(function(ajax) { - var send = ajax.prototype.send; - var open = ajax.prototype.open; - var setRequestHeader = ajax.prototype.setRequestHeader; - ajax.prototype._flutter_inappwebview_url = null; - ajax.prototype._flutter_inappwebview_method = null; - ajax.prototype._flutter_inappwebview_isAsync = null; - ajax.prototype._flutter_inappwebview_user = null; - ajax.prototype._flutter_inappwebview_password = null; - ajax.prototype._flutter_inappwebview_password = null; - ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false; - ajax.prototype._flutter_inappwebview_request_headers = {}; - function convertRequestResponse(request, callback) { - if (request.response != null && request.responseType != null) { - switch (request.responseType) { - case 'arraybuffer': - callback(new Uint8Array(request.response)); - return; - case 'blob': - const reader = new FileReader(); - reader.addEventListener('loadend', function() { - callback(new Uint8Array(reader.result)); - }); - reader.readAsArrayBuffer(blob); - return; - case 'document': - callback(request.response.documentElement.outerHTML); - return; - case 'json': - callback(request.response); - return; - }; +public class InterceptAjaxRequestJS { + + public static let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptAjaxRequest" } - callback(null); - }; - ajax.prototype.open = function(method, url, isAsync, user, password) { - isAsync = (isAsync != null) ? isAsync : true; - this._flutter_inappwebview_url = url; - this._flutter_inappwebview_method = method; - this._flutter_inappwebview_isAsync = isAsync; - this._flutter_inappwebview_user = user; - this._flutter_inappwebview_password = password; - this._flutter_inappwebview_request_headers = {}; - open.call(this, method, url, isAsync, user, password); - }; - ajax.prototype.setRequestHeader = function(header, value) { - this._flutter_inappwebview_request_headers[header] = value; - setRequestHeader.call(this, header, value); - }; - function handleEvent(e) { - var self = this; - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) { - var headers = this.getAllResponseHeaders(); - var responseHeaders = {}; - if (headers != null) { - var arr = headers.trim().split(/[\\r\\n]+/); - arr.forEach(function (line) { - var parts = line.split(': '); - var header = parts.shift(); - var value = parts.join(': '); - responseHeaders[header] = value; - }); - } - convertRequestResponse(this, function(response) { - var ajaxRequest = { - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - readyState: self.readyState, - status: self.status, - responseURL: self.responseURL, - responseType: self.responseType, - response: response, - responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, - responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, - statusText: self.statusText, - responseHeaders, responseHeaders, - event: { - type: e.type, - loaded: e.loaded, - lengthComputable: e.lengthComputable, - total: e.total - } - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - }); - }); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxReadyStateChange" } - }; - ajax.prototype.send = function(data) { - var self = this; - var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false; - if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) { - if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) { - this._flutter_inappwebview_already_onreadystatechange_wrapped = true; - var onreadystatechange = this.onreadystatechange; - this.onreadystatechange = function() { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) { - var headers = this.getAllResponseHeaders(); - var responseHeaders = {}; - if (headers != null) { - var arr = headers.trim().split(/[\\r\\n]+/); - arr.forEach(function (line) { - var parts = line.split(': '); - var header = parts.shift(); - var value = parts.join(': '); - responseHeaders[header] = value; - }); - } - convertRequestResponse(this, function(response) { - var ajaxRequest = { - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - readyState: self.readyState, - status: self.status, - responseURL: self.responseURL, - responseType: self.responseType, - response: response, - responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, - responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, - statusText: self.statusText, - responseHeaders: responseHeaders - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); - return; - }; - } - if (onreadystatechange != null) { - onreadystatechange(); - } - }); - }); - } else if (onreadystatechange != null) { - onreadystatechange(); - } - }; - } - this.addEventListener('loadstart', handleEvent); - this.addEventListener('load', handleEvent); - this.addEventListener('loadend', handleEvent); - this.addEventListener('progress', handleEvent); - this.addEventListener('error', handleEvent); - this.addEventListener('abort', handleEvent); - this.addEventListener('timeout', handleEvent); - \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) { - var ajaxRequest = { - data: data, - method: self._flutter_inappwebview_method, - url: self._flutter_inappwebview_url, - isAsync: self._flutter_inappwebview_isAsync, - user: self._flutter_inappwebview_user, - password: self._flutter_inappwebview_password, - withCredentials: self.withCredentials, - headers: self._flutter_inappwebview_request_headers, - responseType: self.responseType - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { - if (result != null) { - switch (result) { - case 0: - self.abort(); + + public static func FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnAjaxProgress" + } + + public static func FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._interceptOnlyAsyncAjaxRequests" + } + + public static func INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool, initialUseOnAjaxReadyStateChange: Bool = false, initialUseOnAjaxProgress: Bool = false) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: initialUseOnAjaxReadyStateChange, initialUseOnAjaxProgress: initialUseOnAjaxProgress), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) = \(onlyAsync);", + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: [] + ); + } + + public static func INTERCEPT_AJAX_REQUEST_JS_SOURCE(initialUseOnAjaxReadyStateChange: Bool, initialUseOnAjaxProgress: Bool) -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) = true; + \(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) = \(initialUseOnAjaxReadyStateChange); + \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) = \(initialUseOnAjaxProgress); + (function(ajax) { + var send = ajax.prototype.send; + var open = ajax.prototype.open; + var setRequestHeader = ajax.prototype.setRequestHeader; + ajax.prototype._flutter_inappwebview_url = null; + ajax.prototype._flutter_inappwebview_method = null; + ajax.prototype._flutter_inappwebview_isAsync = null; + ajax.prototype._flutter_inappwebview_user = null; + ajax.prototype._flutter_inappwebview_password = null; + ajax.prototype._flutter_inappwebview_password = null; + ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false; + ajax.prototype._flutter_inappwebview_request_headers = {}; + function convertRequestResponse(request, callback) { + if (request.response != null && request.responseType != null) { + switch (request.responseType) { + case 'arraybuffer': + callback(new Uint8Array(request.response)); + return; + case 'blob': + const reader = new FileReader(); + reader.addEventListener('loadend', function() { + callback(new Uint8Array(reader.result)); + }); + reader.readAsArrayBuffer(blob); + return; + case 'document': + callback(request.response.documentElement.outerHTML); + return; + case 'json': + callback(request.response); return; }; - if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); - if (result.headers != null) { - result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; - } else { - result.headers = { 'Content-Type': formDataContentType }; - } - } - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) { - data = result.data; - } else if (result.data.length > 0) { - data = new Uint8Array(result.data); + } + callback(null); + }; + ajax.prototype.open = function(method, url, isAsync, user, password) { + isAsync = (isAsync != null) ? isAsync : true; + this._flutter_inappwebview_url = url; + this._flutter_inappwebview_method = method; + this._flutter_inappwebview_isAsync = isAsync; + this._flutter_inappwebview_user = user; + this._flutter_inappwebview_password = password; + this._flutter_inappwebview_request_headers = {}; + open.call(this, method, url, isAsync, user, password); + }; + ajax.prototype.setRequestHeader = function(header, value) { + this._flutter_inappwebview_request_headers[header] = value; + setRequestHeader.call(this, header, value); + }; + function handleEvent(e) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) === false || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) == null || \(FLAG_VARIABLE_FOR_ON_AJAX_PROGRESS()) === false) { + return; + } + var self = this; + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\\r\\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); } - self.withCredentials = result.withCredentials; - if (result.responseType != null && self._flutter_inappwebview_isAsync) { - self.responseType = result.responseType; - }; - if (result.headers != null) { - for (var header in result.headers) { - var value = result.headers[header]; - var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; - if (flutter_inappwebview_value == null) { - self._flutter_inappwebview_request_headers[header] = value; - } else { - self._flutter_inappwebview_request_headers[header] += ', ' + value; + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders, responseHeaders, + event: { + type: e.type, + loaded: e.loaded, + lengthComputable: e.lengthComputable, + total: e.total + } + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + }); + }); + } + }; + ajax.prototype.send = function(data) { + var self = this; + var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE()) === false; + if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true)) { + if (\(FLAG_VARIABLE_FOR_ON_AJAX_READY_STATE_CHANGE()) === true && !this._flutter_inappwebview_already_onreadystatechange_wrapped) { + this._flutter_inappwebview_already_onreadystatechange_wrapped = true; + var realOnreadystatechange = this.onreadystatechange; + this.onreadystatechange = function() { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE()) == true) { + var headers = this.getAllResponseHeaders(); + var responseHeaders = {}; + if (headers != null) { + var arr = headers.trim().split(/[\\r\\n]+/); + arr.forEach(function (line) { + var parts = line.split(': '); + var header = parts.shift(); + var value = parts.join(': '); + responseHeaders[header] = value; + }); + } + convertRequestResponse(this, function(response) { + var ajaxRequest = { + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + readyState: self.readyState, + status: self.status, + responseURL: self.responseURL, + responseType: self.responseType, + response: response, + responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null, + responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null, + statusText: self.statusText, + responseHeaders: responseHeaders + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + } + if (realOnreadystatechange != null) { + realOnreadystatechange(); + } + }); + }); + } else if (realOnreadystatechange != null) { + realOnreadystatechange(); } - setRequestHeader.call(self, header, value); }; } - if ((self._flutter_inappwebview_method != result.method && result.method != null) || - (self._flutter_inappwebview_url != result.url && result.url != null) || - (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) || - (self._flutter_inappwebview_user != result.user && result.user != null) || - (self._flutter_inappwebview_password != result.password && result.password != null)) { - self.abort(); - self.open(result.method, result.url, result.isAsync, result.user, result.password); - } + this.addEventListener('loadstart', handleEvent); + this.addEventListener('load', handleEvent); + this.addEventListener('loadend', handleEvent); + this.addEventListener('progress', handleEvent); + this.addEventListener('error', handleEvent); + this.addEventListener('abort', handleEvent); + this.addEventListener('timeout', handleEvent); + \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(data).then(function(data) { + var ajaxRequest = { + data: data, + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + responseType: self.responseType + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + switch (result) { + case 0: + self.abort(); + return; + }; + if (result.data != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) && result.data.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.data); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.data) || result.data == null) { + data = result.data; + } else if (result.data.length > 0) { + data = new Uint8Array(result.data); + } + self.withCredentials = result.withCredentials; + if (result.responseType != null && self._flutter_inappwebview_isAsync) { + self.responseType = result.responseType; + }; + if (result.headers != null) { + for (var header in result.headers) { + var value = result.headers[header]; + var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; + if (flutter_inappwebview_value == null) { + self._flutter_inappwebview_request_headers[header] = value; + } else { + self._flutter_inappwebview_request_headers[header] += ', ' + value; + } + setRequestHeader.call(self, header, value); + }; + } + if ((self._flutter_inappwebview_method != result.method && result.method != null) || + (self._flutter_inappwebview_url != result.url && result.url != null) || + (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) || + (self._flutter_inappwebview_user != result.user && result.user != null) || + (self._flutter_inappwebview_password != result.password && result.password != null)) { + self.abort(); + self.open(result.method, result.url, result.isAsync, result.user, result.password); + } + } + send.call(self, data); + }); + }); + } else { + send.call(this, data); } - send.call(self, data); - }); - }); - } else { - send.call(this, data); + }; + })(window.XMLHttpRequest); + """ } - }; -})(window.XMLHttpRequest); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift index 14539811c..953fa3aa8 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift @@ -7,147 +7,157 @@ import Foundation -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest" - -let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript( - groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: INTERCEPT_FETCH_REQUEST_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true; -(function(fetch) { - if (fetch == null) { - return; - } - window.fetch = async function(resource, init) { - if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == true) { - var fetchRequest = { - url: null, - method: null, - headers: null, - body: null, - mode: null, - credentials: null, - cache: null, - redirect: null, - referrer: null, - referrerPolicy: null, - integrity: null, - keepalive: null - }; - if (resource instanceof Request) { - fetchRequest.url = resource.url; - fetchRequest.method = resource.method; - fetchRequest.headers = resource.headers; - fetchRequest.body = resource.body; - fetchRequest.mode = resource.mode; - fetchRequest.credentials = resource.credentials; - fetchRequest.cache = resource.cache; - fetchRequest.redirect = resource.redirect; - fetchRequest.referrer = resource.referrer; - fetchRequest.referrerPolicy = resource.referrerPolicy; - fetchRequest.integrity = resource.integrity; - fetchRequest.keepalive = resource.keepalive; - } else { - fetchRequest.url = resource != null ? resource.toString() : null; - if (init != null) { - fetchRequest.method = init.method; - fetchRequest.headers = init.headers; - fetchRequest.body = init.body; - fetchRequest.mode = init.mode; - fetchRequest.credentials = init.credentials; - fetchRequest.cache = init.cache; - fetchRequest.redirect = init.redirect; - fetchRequest.referrer = init.referrer; - fetchRequest.referrerPolicy = init.referrerPolicy; - fetchRequest.integrity = init.integrity; - fetchRequest.keepalive = init.keepalive; - } - } - if (fetchRequest.headers instanceof Headers) { - fetchRequest.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers); - } - fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials); - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) { - fetchRequest.body = body; - return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { - if (result != null) { - switch (result.action) { - case 0: - var controller = new AbortController(); +public class InterceptFetchRequestJS { + + public static let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT" + public static func FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useShouldInterceptFetchRequest" + } + + public static func INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: INTERCEPT_FETCH_REQUEST_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func INTERCEPT_FETCH_REQUEST_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) = true; + (function(fetch) { + if (fetch == null) { + return; + } + window.fetch = async function(resource, init) { + if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE()) == true) { + var fetchRequest = { + url: null, + method: null, + headers: null, + body: null, + mode: null, + credentials: null, + cache: null, + redirect: null, + referrer: null, + referrerPolicy: null, + integrity: null, + keepalive: null + }; + if (resource instanceof Request) { + fetchRequest.url = resource.url; + fetchRequest.method = resource.method; + fetchRequest.headers = resource.headers; + fetchRequest.body = resource.body; + fetchRequest.mode = resource.mode; + fetchRequest.credentials = resource.credentials; + fetchRequest.cache = resource.cache; + fetchRequest.redirect = resource.redirect; + fetchRequest.referrer = resource.referrer; + fetchRequest.referrerPolicy = resource.referrerPolicy; + fetchRequest.integrity = resource.integrity; + fetchRequest.keepalive = resource.keepalive; + } else { + fetchRequest.url = resource != null ? resource.toString() : null; if (init != null) { - init.signal = controller.signal; - } else { - init = { - signal: controller.signal - }; - } - controller.abort(); - break; - } - if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) { - var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body); - if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { - var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); - if (result.headers != null) { - result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; - } else { - result.headers = { 'Content-Type': formDataContentType }; + fetchRequest.method = init.method; + fetchRequest.headers = init.headers; + fetchRequest.body = init.body; + fetchRequest.mode = init.mode; + fetchRequest.credentials = init.credentials; + fetchRequest.cache = init.cache; + fetchRequest.redirect = init.redirect; + fetchRequest.referrer = init.referrer; + fetchRequest.referrerPolicy = init.referrerPolicy; + fetchRequest.integrity = init.integrity; + fetchRequest.keepalive = init.keepalive; } } + if (fetchRequest.headers instanceof Headers) { + fetchRequest.headers = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertHeadersToJson(fetchRequest.headers); + } + fetchRequest.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertCredentialsToJson(fetchRequest.credentials); + return \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertBodyRequest(fetchRequest.body).then(function(body) { + fetchRequest.body = body; + return window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { + if (result != null) { + switch (result.action) { + case 0: + var controller = new AbortController(); + if (init != null) { + init.signal = controller.signal; + } else { + init = { + signal: controller.signal + }; + } + controller.abort(); + break; + } + if (result.body != null && !\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) && result.body.length > 0) { + var bodyString = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).arrayBufferToString(result.body); + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isBodyFormData(bodyString)) { + var formDataContentType = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + resource = result.url; + if (init == null) { + init = {}; + } + if (result.method != null && result.method.length > 0) { + init.method = result.method; + } + if (result.headers != null && Object.keys(result.headers).length > 0) { + init.headers = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToHeaders(result.headers); + } + if (\(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).isString(result.body) || result.body == null) { + init.body = result.body; + } else if (result.body.length > 0) { + init.body = new Uint8Array(result.body); + } + if (result.mode != null && result.mode.length > 0) { + init.mode = result.mode; + } + if (result.credentials != null) { + init.credentials = \(JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME()).convertJsonToCredential(result.credentials); + } + if (result.cache != null && result.cache.length > 0) { + init.cache = result.cache; + } + if (result.redirect != null && result.redirect.length > 0) { + init.redirect = result.redirect; + } + if (result.referrer != null && result.referrer.length > 0) { + init.referrer = result.referrer; + } + if (result.referrerPolicy != null && result.referrerPolicy.length > 0) { + init.referrerPolicy = result.referrerPolicy; + } + if (result.integrity != null && result.integrity.length > 0) { + init.integrity = result.integrity; + } + if (result.keepalive != null) { + init.keepalive = result.keepalive; + } + return fetch(resource, init); + } + return fetch(resource, init); + }); + }); + } else { + return fetch(resource, init); } - resource = result.url; - if (init == null) { - init = {}; - } - if (result.method != null && result.method.length > 0) { - init.method = result.method; - } - if (result.headers != null && Object.keys(result.headers).length > 0) { - init.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) { - init.body = result.body; - } else if (result.body.length > 0) { - init.body = new Uint8Array(result.body); - } - if (result.mode != null && result.mode.length > 0) { - init.mode = result.mode; - } - if (result.credentials != null) { - init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToCredential(result.credentials); - } - if (result.cache != null && result.cache.length > 0) { - init.cache = result.cache; - } - if (result.redirect != null && result.redirect.length > 0) { - init.redirect = result.redirect; - } - if (result.referrer != null && result.referrer.length > 0) { - init.referrer = result.referrer; - } - if (result.referrerPolicy != null && result.referrerPolicy.length > 0) { - init.referrerPolicy = result.referrerPolicy; - } - if (result.integrity != null && result.integrity.length > 0) { - init.integrity = result.integrity; - } - if (result.keepalive != null) { - init.keepalive = result.keepalive; - } - return fetch(resource, init); - } - return fetch(resource, init); - }); - }); - } else { - return fetch(resource, init); + }; + })(window.fetch); + """ } - }; -})(window.fetch); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift index 1549c28be..0e42a3734 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift @@ -7,237 +7,287 @@ import Foundation -let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" - -let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: JAVASCRIPT_BRIDGE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: ["callHandler"]) - -let JAVASCRIPT_BRIDGE_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME) = {}; -\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {}; -window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - var _callHandlerID = setTimeout(function(){}); - window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} ); - return new Promise(function(resolve, reject) { - window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject}; - }); -}; -\(WEB_MESSAGE_LISTENER_JS_SOURCE) -\(UTIL_JS_SOURCE) -""" - -let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; +public class JavaScriptBridgeJS { + private static var _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview" + public static func set_JAVASCRIPT_BRIDGE_NAME(bridgeName: String) { + _JAVASCRIPT_BRIDGE_NAME = bridgeName + } + public static func get_JAVASCRIPT_BRIDGE_NAME() -> String { + return _JAVASCRIPT_BRIDGE_NAME + } + + public static let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT" + + public static let VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET" -let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util" + public static func JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret: String, allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + let source = JAVASCRIPT_BRIDGE_JS_SOURCE().replacingOccurrences(of: VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, with: expectedBridgeSecret) + return PluginScript( + groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: source, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: ["callHandler"]) + } -/* - https://github.com/github/fetch/blob/master/fetch.js - */ -let UTIL_JS_SOURCE = """ -\(JAVASCRIPT_UTIL_VAR_NAME) = { - support: { - searchParams: 'URLSearchParams' in window, - iterable: 'Symbol' in window && 'iterator' in Symbol, - blob: - 'FileReader' in window && - 'Blob' in window && - (function() { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - })(), - formData: 'FormData' in window, - arrayBuffer: 'ArrayBuffer' in window - }, - isDataView: function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj); - }, - fileReaderReady: function(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result); - }; - reader.onerror = function() { - reject(reader.error); - }; - }); - }, - readBlobAsArrayBuffer: function(blob) { - var reader = new FileReader(); - var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader); - reader.readAsArrayBuffer(blob); - return promise; - }, - convertBodyToArrayBuffer: function(body) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ]; - var isArrayBufferView = null; - if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) { - isArrayBufferView = - ArrayBuffer.isView || - function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + public static func JAVASCRIPT_BRIDGE_JS_SOURCE() -> String { + return """ + window.\(get_JAVASCRIPT_BRIDGE_NAME()) = {}; + \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME()) = {}; + (function(window) { + var bridgeSecret = '\(VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET)'; + var _JSON_stringify; + var _Array_slice; + var _setTimeout; + var _Promise; + var _UserMessageHandler; + var _postMessage; + try { + _JSON_stringify = window.JSON.stringify; + _Array_slice = window.Array.prototype.slice; + _Array_slice.call = window.Function.prototype.call; + _setTimeout = window.setTimeout; + _Promise = window.Promise; + _UserMessageHandler = window.webkit.messageHandlers['callHandler']; + _postMessage = _UserMessageHandler.postMessage; + _postMessage.call = window.Function.prototype.call; + } catch (_) { return; } + window.\(get_JAVASCRIPT_BRIDGE_NAME()).callHandler = function() { + var _windowId = \(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()); + var _callHandlerID = _setTimeout(function(){}); + _postMessage.call(_UserMessageHandler, { + 'handlerName': arguments[0], + '_callHandlerID': _callHandlerID, + '_bridgeSecret': bridgeSecret, + 'args': _JSON_stringify(_Array_slice.call(arguments, 1)), + '_windowId': _windowId + }); + return new _Promise(function(resolve, reject) { + try { + (window.top === window ? window : window.top).\(get_JAVASCRIPT_BRIDGE_NAME())[_callHandlerID] = {resolve: resolve, reject: reject}; + } catch (e) { + resolve(); + } + }); }; - } + })(window); + \(WebMessageListenerJS.WEB_MESSAGE_LISTENER_JS_SOURCE()) + \(UTIL_JS_SOURCE()) + """ + } - var bodyUsed = false; + public static let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; - this._bodyInit = body; - if (!body) { - this._bodyText = ''; - } else if (typeof body === 'string') { - this._bodyText = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body; - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString(); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer); - this._bodyInit = new Blob([this._bodyArrayBuffer]); - } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body); - } else { - this._bodyText = body = Object.prototype.toString.call(body); - } + public static func JAVASCRIPT_UTIL_VAR_NAME() -> String { + return "window.\(get_JAVASCRIPT_BRIDGE_NAME())._Util" + } - this.blob = function () { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob); - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])); - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob'); - } else { - return Promise.resolve(new Blob([this._bodyText])); + /* + https://github.com/github/fetch/blob/master/fetch.js + */ + public static func UTIL_JS_SOURCE() -> String { + return """ + \(JAVASCRIPT_UTIL_VAR_NAME()) = { + support: { + searchParams: 'URLSearchParams' in window, + iterable: 'Symbol' in window && 'iterator' in Symbol, + blob: + 'FileReader' in window && + 'Blob' in window && + (function() { + try { + new Blob(); + return true; + } catch (e) { + return false; + } + })(), + formData: 'FormData' in window, + arrayBuffer: 'ArrayBuffer' in window + }, + isDataView: function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj); + }, + fileReaderReady: function(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }); + }, + readBlobAsArrayBuffer: function(blob) { + var reader = new FileReader(); + var promise = \(JAVASCRIPT_UTIL_VAR_NAME()).fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise; + }, + convertBodyToArrayBuffer: function(body) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + var isArrayBufferView = null; + if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer) { + isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + }; + } + + var bodyUsed = false; + + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME()).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME()).isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME()).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + this.blob = function () { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob); + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])); + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob'); + } else { + return Promise.resolve(new Blob([this._bodyText])); + } + }; + + if (this._bodyArrayBuffer) { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ); + } else { + return Promise.resolve(this._bodyArrayBuffer); + } + } + return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME()).readBlobAsArrayBuffer); + }, + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + convertBodyRequest: function(body) { + if (body == null) { + return new Promise((resolve, reject) => resolve(null)); + } + if (\(JAVASCRIPT_UTIL_VAR_NAME()).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME()).support.searchParams && body instanceof URLSearchParams)) { + return new Promise((resolve, reject) => resolve(body.toString())); + } + if (window.Response != null) { + return new Response(body).arrayBuffer().then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + } + return \(JAVASCRIPT_UTIL_VAR_NAME()).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + }, + arrayBufferToString: function(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') >= 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.substr(2, 40); + return 'multipart/form-data; boundary=' + boundary; + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + for (var header of headers.keys()) { + var value = headers.get(header); + headersObj[header] = value; + } + return headersObj; + }, + convertJsonToHeaders: function(headersJson) { + return new Headers(headersJson); + }, + convertCredentialsToJson: function(credentials) { + var credentialsObj = {}; + if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.protocol = credentials.protocol; + credentialsObj.provider = credentials.provider; + credentialsObj.iconURL = credentials.iconURL; + } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.password = credentials.password; + credentialsObj.iconURL = credentials.iconURL; + } else { + credentialsObj.type = 'default'; + credentialsObj.value = credentials; + } + return credentialsObj; + }, + convertJsonToCredential: function(credentialsJson) { + var credentials; + if (window.FederatedCredential != null && credentialsJson.type === 'federated') { + credentials = new FederatedCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + protocol: credentialsJson.protocol, + provider: credentialsJson.provider, + iconURL: credentialsJson.iconURL + }); + } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { + credentials = new PasswordCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + password: credentialsJson.password, + iconURL: credentialsJson.iconURL + }); + } else { + credentials = credentialsJson.value == null ? undefined : credentialsJson.value; + } + return credentials; } }; - - if (this._bodyArrayBuffer) { - if (bodyUsed) { - return Promise.reject(new TypeError('Already read')); - } - bodyUsed = true; - if (ArrayBuffer.isView(this._bodyArrayBuffer)) { - return Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer.byteOffset, - this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength - ) - ); - } else { - return Promise.resolve(this._bodyArrayBuffer); - } - } - return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer); - }, - isString: function(variable) { - return typeof variable === 'string' || variable instanceof String; - }, - convertBodyRequest: function(body) { - if (body == null) { - return new Promise((resolve, reject) => resolve(null)); - } - if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) { - return new Promise((resolve, reject) => resolve(body.toString())); - } - if (window.Response != null) { - return new Response(body).arrayBuffer().then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - } - return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { - return Array.from(new Uint8Array(arrayBuffer)); - }); - }, - arrayBufferToString: function(arrayBuffer) { - var uint8Array = new Uint8Array(arrayBuffer); - return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, ''); - }, - isBodyFormData: function(bodyString) { - return bodyString.indexOf('------WebKitFormBoundary') >= 0; - }, - getFormDataContentType: function(bodyString) { - var boundary = bodyString.substr(2, 40); - return 'multipart/form-data; boundary=' + boundary; - }, - convertHeadersToJson: function(headers) { - var headersObj = {}; - for (var header of headers.keys()) { - var value = headers.get(header); - headersObj[header] = value; - } - return headersObj; - }, - convertJsonToHeaders: function(headersJson) { - return new Headers(headersJson); - }, - convertCredentialsToJson: function(credentials) { - var credentialsObj = {}; - if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.protocol = credentials.protocol; - credentialsObj.provider = credentials.provider; - credentialsObj.iconURL = credentials.iconURL; - } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.password = credentials.password; - credentialsObj.iconURL = credentials.iconURL; - } else { - credentialsObj.type = 'default'; - credentialsObj.value = credentials; - } - return credentialsObj; - }, - convertJsonToCredential: function(credentialsJson) { - var credentials; - if (window.FederatedCredential != null && credentialsJson.type === 'federated') { - credentials = new FederatedCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - protocol: credentialsJson.protocol, - provider: credentialsJson.provider, - iconURL: credentialsJson.iconURL - }); - } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { - credentials = new PasswordCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - password: credentialsJson.password, - iconURL: credentialsJson.iconURL - }); - } else { - credentials = credentialsJson.value == null ? undefined : credentialsJson.value; - } - return credentials; + """ } -}; -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift index 828de9ef5..18a5e496b 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnLoadResourceJS.swift @@ -7,33 +7,44 @@ import Foundation -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" -let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource" - -let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_LOAD_RESOURCE_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_LOAD_RESOURCE_JS_SOURCE = """ -\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true; -(function() { - var observer = new PerformanceObserver(function(list) { - list.getEntries().forEach(function(entry) { - if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) { - var resource = { - "url": entry.name, - "initiatorType": entry.initiatorType, - "startTime": entry.startTime, - "duration": entry.duration - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource); - } - }); - }); - observer.observe({entryTypes: ['resource']}); -})(); -""" +public class OnLoadResourceJS { + + public static let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT" + + public static func FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._useOnLoadResource" + } + + public static func ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_LOAD_RESOURCE_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_LOAD_RESOURCE_JS_SOURCE() -> String { + return """ + \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) = true; + (function() { + var observer = new PerformanceObserver(function(list) { + list.getEntries().forEach(function(entry) { + if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE()) == true) { + var resource = { + "url": entry.name, + "initiatorType": entry.initiatorType, + "startTime": entry.startTime, + "duration": entry.duration + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onLoadResource", resource); + } + }); + }); + observer.observe({entryTypes: ['resource']}); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift index 4418c042a..2c6b29d24 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnScrollChangedJS.swift @@ -7,27 +7,33 @@ import Foundation -let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT" - -let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: ["onScrollChanged"]) - -let ON_SCROLL_CHANGED_EVENT_JS_SOURCE = """ -(function(){ - document.addEventListener('scroll', function(e) { - var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE); - window.webkit.messageHandlers["onScrollChanged"].postMessage( - { - x: window.scrollX, - y: window.scrollY, - _windowId: _windowId - } - ); - }); -})(); -""" +public class OnScrollChangedJS { + + public static let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_SCROLL_CHANGED_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + document.addEventListener('scroll', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onScrollChanged', { + 'x': window.scrollX, + 'y': window.scrollY + } + ); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift index 345b5eced..d9221d10e 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowBlurEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ON_WINDOW_BLUR_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('blur', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur'); - }); -})(); -""" +public class OnWindowBlurEventJS { + + public static let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_BLUR_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_BLUR_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('blur', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowBlur'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift index 9ebda3e96..b08bb0889 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OnWindowFocusEventJS.swift @@ -7,20 +7,29 @@ import Foundation -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" - -let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """ -(function(){ - window.addEventListener('focus', function(e) { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus'); - }); -})(); -""" +public class OnWindowFocusEventJS { + + public static let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ON_WINDOW_FOCUS_EVENT_JS_SOURCE() -> String { + return """ + (function(){ + window.addEventListener('focus', function(e) { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWindowFocus'); + }); + })(); + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift index a1c6637d7..d84c14ee3 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/OriginalViewPortMetaTagContentJS.swift @@ -7,25 +7,34 @@ import Foundation -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true, - requiredInAllContentWorlds: false, - messageHandlerNames: []) - -let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """ -window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = ""; -(function() { - var metaTagNodes = document.head.getElementsByTagName('meta'); - for (var i = 0; i < metaTagNodes.length; i++) { - var metaTagNode = metaTagNodes[i]; - if (metaTagNode.name === "viewport") { - window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content; - } +public class OriginalViewPortMetaTagContentJS { + + public static let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT" + + // This plugin is only for main frame + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?) -> PluginScript { + return PluginScript( + groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE(), + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static func ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE() -> String { + return """ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = ""; + (function() { + var metaTagNodes = document.head.getElementsByTagName('meta'); + for (var i = 0; i < metaTagNodes.length; i++) { + var metaTagNode = metaTagNodes[i]; + if (metaTagNode.name === "viewport") { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent = metaTagNode.content; + } + } + })(); + """ } -})(); -""" +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift index 5ac12691f..fbf29f50f 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PrintJS.swift @@ -7,18 +7,26 @@ import Foundation -let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" - -let PRINT_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PRINT_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -let PRINT_JS_SOURCE = """ -window.print = function() { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href); +public class PrintJS { + + public static let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT" + + public static func PRINT_JS_PLUGIN_SCRIPT(allowedOriginRules: [String]?, forMainFrameOnly: Bool) -> PluginScript { + return PluginScript( + groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PRINT_JS_SOURCE(), + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + public static func PRINT_JS_SOURCE() -> String { + return """ + window.print = function() { + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler("onPrintRequest", window.location.href); + } + """ + } } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift index d2ef3e2a5..27c076be2 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/PromisePolyfillJS.swift @@ -8,20 +8,25 @@ import Foundation import WebKit -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT" - -let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript( - groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, - source: PROMISE_POLYFILL_JS_SOURCE, - injectionTime: .atDocumentStart, - forMainFrameOnly: false, - requiredInAllContentWorlds: true, - messageHandlerNames: []) - -// https://github.com/tildeio/rsvp.js -let PROMISE_POLYFILL_JS_SOURCE = """ -if (window.Promise == null) { - !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: PROMISE_POLYFILL_JS_SOURCE, + injectionTime: .atDocumentStart, + forMainFrameOnly: forMainFrameOnly, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: true, + messageHandlerNames: []) + } + + // https://github.com/tildeio/rsvp.js + public static let PROMISE_POLYFILL_JS_SOURCE = """ + if (window.Promise == null) { + !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s PluginScript { + return PluginScript( + groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME, + source: NOT_SUPPORT_ZOOM_JS_SOURCE, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true, + allowedOriginRules: allowedOriginRules, + requiredInAllContentWorlds: false, + messageHandlerNames: []) + } + + public static let NOT_SUPPORT_ZOOM_JS_SOURCE = """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + + public static func SUPPORT_ZOOM_JS_SOURCE() -> String { + return """ + (function() { + var meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._originalViewPortMetaTagContent); + document.getElementsByTagName('head')[0].appendChild(meta); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift index 184458413..e41494d88 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageChannelJS.swift @@ -7,4 +7,10 @@ import Foundation -let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels" +public class WebMessageChannelJS { + + public static func WEB_MESSAGE_CHANNELS_VARIABLE_NAME() -> String { + return "window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())._webMessageChannels" + } + +} diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift index ab8edfaed..6740765ac 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WebMessageListenerJS.swift @@ -7,110 +7,37 @@ import Foundation -let WEB_MESSAGE_LISTENER_JS_SOURCE = """ -function FlutterInAppWebViewWebMessageListener(jsObjectName) { - this.jsObjectName = jsObjectName; - this.listeners = []; - this.onmessage = null; -} -FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) { - var message = { - "data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null), - "type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0 - }; - window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); -}; -FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { - if (listener == null) { - return; - } - this.listeners.push(listener); -}; -FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) { - if (listener == null) { - return; - } - var index = this.listeners.indexOf(listener); - if (index >= 0) { - this.listeners.splice(index, 1); - } -}; - -window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) { - // replace ipv4 address if any - var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/); - if (ipv4) { - ip_string = ipv4[1]; - ipv4 = ipv4[2].match(/[0-9]+/g); - for (var i = 0;i < 4;i ++) { - var byte = parseInt(ipv4[i],10); - ipv4[i] = ("0" + byte.toString(16)).substr(-2); - } - ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3]; - } - - // take care of leading and trailing :: - ip_string = ip_string.replace(/^:|:$/g, ''); - - var ipv6 = ip_string.split(':'); - - for (var i = 0; i < ipv6.length; i ++) { - var hex = ipv6[i]; - if (hex != "") { - // normalize leading zeros - ipv6[i] = ("0000" + hex).substr(-4); - } - else { - // normalize grouped zeros :: - hex = []; - for (var j = ipv6.length; j <= 8; j ++) { - hex.push('0000'); +public class WebMessageListenerJS { + + public static func WEB_MESSAGE_LISTENER_JS_SOURCE() -> String { + return """ + function FlutterInAppWebViewWebMessageListener(jsObjectName) { + this.jsObjectName = jsObjectName; + this.listeners = []; + this.onmessage = null; + } + FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) { + var message = { + "data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null), + "type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0 + }; + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessageListenerPostMessageReceived', {jsObjectName: this.jsObjectName, message: message}); + }; + FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { + if (listener == null) { + return; } - ipv6[i] = hex.join(':'); - } - } - - return ipv6.join(':'); -} - -window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) { - for (var rule of allowedOriginRules) { - if (rule === "*") { - return true; - } - if (scheme == null || scheme === "") { - continue; - } - if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) { - continue; - } - var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port; - var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port; - var IPv6 = null; - if (rule.host != null && rule.host[0] === "[") { - try { - IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1)); - } catch {} - } - var hostIPv6 = null; - try { - hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host); - } catch {} - - var schemeAllowed = scheme == rule.scheme; - - var hostAllowed = rule.host == null || - rule.host === "" || - host === rule.host || - (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) || - (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6); - - var portAllowed = rulePort === currentPort; - - if (schemeAllowed && hostAllowed && portAllowed) { - return true; - } + this.listeners.push(listener); + }; + FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) { + if (listener == null) { + return; + } + var index = this.listeners.indexOf(listener); + if (index >= 0) { + this.listeners.splice(index, 1); + } + }; + """ } - return false; } -""" diff --git a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift index 8b3a1879f..173167416 100644 --- a/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift +++ b/flutter_inappwebview_macos/macos/Classes/PluginScriptsJS/WindowIdJS.swift @@ -7,13 +7,20 @@ import Foundation -let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" - -let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId" - -let WINDOW_ID_INITIALIZE_JS_SOURCE = """ -(function() { - \(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); - return \(WINDOW_ID_VARIABLE_JS_SOURCE); -})() -""" +public class WindowIdJS { + + public static let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT" + + public static func WINDOW_ID_VARIABLE_JS_SOURCE() -> String { + return "window._\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME())_windowId" + } + + public static func WINDOW_ID_INITIALIZE_JS_SOURCE() -> String { + return """ + (function() { + \(WINDOW_ID_VARIABLE_JS_SOURCE()) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE); + return \(WINDOW_ID_VARIABLE_JS_SOURCE()); + })() + """ + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift index 7514dd4f5..d2000c8b1 100644 --- a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift +++ b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobChannelDelegate.swift @@ -55,6 +55,7 @@ public class PrintJobChannelDelegate: ChannelDelegate { } deinit { + debugPrint("PrintJobChannelDelegate - dealloc") dispose() } } diff --git a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift index 9b76a4085..400f7160d 100644 --- a/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift +++ b/flutter_inappwebview_macos/macos/Classes/PrintJob/PrintJobController.swift @@ -24,6 +24,7 @@ public class PrintJobController: NSObject, Disposable { var channelDelegate: PrintJobChannelDelegate? var state = PrintJobState.created var creationTime = Int64(Date().timeIntervalSince1970 * 1000) + var disposeWhenDidRun = false private var completionHandler: PrintJobController.CompletionHandler? public typealias CompletionHandler = (_ printOperation: NSPrintOperation, @@ -36,11 +37,9 @@ public class PrintJobController: NSObject, Disposable { super.init() self.job = job self.settings = settings - if let registrar = plugin.registrar { - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) - } + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: plugin.registrar.messenger) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) } public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) { @@ -57,11 +56,16 @@ public class PrintJobController: NSObject, Disposable { @objc func printOperationDidRun(printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) { - state = success ? .completed : .canceled - channelDelegate?.onComplete(completed: success, error: nil) - if let completionHandler = completionHandler { - completionHandler(printOperation, success, contextInfo) - self.completionHandler = nil + DispatchQueue.main.async { [weak self] in + self?.state = success ? .completed : .canceled + self?.channelDelegate?.onComplete(completed: success, error: nil) + if let completionHandler = self?.completionHandler { + completionHandler(printOperation, success, contextInfo) + self?.completionHandler = nil + } + if let disposeWhenDidRun = self?.disposeWhenDidRun, disposeWhenDidRun { + self?.dispose() + } } } @@ -73,7 +77,7 @@ public class PrintJobController: NSObject, Disposable { return PrintJobInfo.init(fromPrintJobController: self) } - public func disposeNoDismiss() { + public func dispose() { channelDelegate?.dispose() channelDelegate = nil completionHandler = nil @@ -82,12 +86,7 @@ public class PrintJobController: NSObject, Disposable { plugin = nil } - public func dispose() { - channelDelegate?.dispose() - channelDelegate = nil - completionHandler = nil - job = nil - plugin?.printJobManager?.jobs[id] = nil - plugin = nil + deinit { + debugPrint("PrintJobController - dealloc") } } diff --git a/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift b/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift new file mode 100644 index 000000000..082d55bf2 --- /dev/null +++ b/flutter_inappwebview_macos/macos/Classes/ProxyManager.swift @@ -0,0 +1,248 @@ +// +// ProxyManager.swift +// flutter_inappwebview +// + +import Foundation +import WebKit +import FlutterMacOS + +@available(macOS 14.0, *) +public class ProxyManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_proxycontroller" + + private var plugin: InAppWebViewFlutterPlugin? + + init(plugin: InAppWebViewFlutterPlugin) { + super.init(channel: FlutterMethodChannel(name: ProxyManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) + self.plugin = plugin + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? [String: Any] + switch call.method { + case "setProxyOverride": + if let args = arguments?["settings"] as? [String:Any?], + let settings = ProxySettings.fromMap(map: args) { + setProxyOverride(settings) + result(true) + } else { + result(false) + } + break + case "clearProxyOverride": + clearProxyOverride() + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func setProxyOverride(_ settings: ProxySettings) { + let proxyConfigurations = settings.toProxyConfigurations() + WKWebsiteDataStore.default().proxyConfigurations = proxyConfigurations + WKWebsiteDataStore.nonPersistent().proxyConfigurations = proxyConfigurations + } + + public func clearProxyOverride() { + WKWebsiteDataStore.default().proxyConfigurations = [] + WKWebsiteDataStore.nonPersistent().proxyConfigurations = [] + } + + public override func dispose() { + super.dispose() + plugin = nil + } + + deinit { + debugPrint("ProxyManager - dealloc") + dispose() + } +} + +@available(macOS 14.0, *) +public class ProxySettings { + var proxyRules: [ProxyRule] + + init( + proxyRules: [ProxyRule] + ) { + self.proxyRules = proxyRules + } + + public static func fromMap(map: [String:Any?]?) -> ProxySettings? { + guard let map = map else { + return nil + } + return ProxySettings( + proxyRules: (map["proxyRules"] as! [[String:Any?]]).map { ProxyRule.fromMap(map: $0)! } + ) + } + + public func toProxyConfigurations() -> [ProxyConfiguration] { + var proxyConfigurations: [ProxyConfiguration] = [] + for rule in proxyRules { + if let proxyConfiguration = rule.toProxyConfiguration() { + proxyConfigurations.append(proxyConfiguration) + } + } + return proxyConfigurations + } +} + +@available(macOS 14.0, *) +public class ProxyRule { + var url: String + var allowFailover: Bool? + var excludedDomains: [String]? + var matchDomains: [String]? + var username: String? + var password: String? + var relayHop1: ProxyRelayHop? + var relayHop2: ProxyRelayHop? + + init( + url: String, + allowFailover: Bool?, + excludedDomains: [String]?, + matchDomains: [String]?, + username: String?, + password: String?, + relayHop1: ProxyRelayHop?, + relayHop2: ProxyRelayHop? + ) { + self.url = url + self.allowFailover = allowFailover + self.excludedDomains = excludedDomains + self.matchDomains = matchDomains + self.username = username + self.password = password + self.relayHop1 = relayHop1 + self.relayHop2 = relayHop2 + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRule? { + guard let map = map else { + return nil + } + return ProxyRule( + url: map["url"] as! String, + allowFailover: map["allowFailover"] as? Bool, + excludedDomains: map["excludedDomains"] as? [String], + matchDomains: map["matchDomains"] as? [String], + username: map["username"] as? String, + password: map["password"] as? String, + relayHop1: ProxyRelayHop.fromMap(map: map["relayHop1"] as? [String:Any?]), + relayHop2: ProxyRelayHop.fromMap(map: map["relayHop2"] as? [String:Any?]) + ) + } + + public func toProxyConfiguration() -> ProxyConfiguration? { + guard let endpointUrl = URL(string: url.contains("://") ? url : "http://" + url), + let port: NWEndpoint.Port = .init(rawValue: UInt16(endpointUrl.port ?? 80)), + let host = endpointUrl.host else { + return nil + } + + var endpointHost = NWEndpoint.Host(host) + if let ipv4 = IPv4Address(host) { + endpointHost = .ipv4(ipv4) + } else if let ipv6 = IPv6Address(host) { + endpointHost = .ipv6(ipv6) + } + let endpoint = NWEndpoint.hostPort(host: endpointHost, port: port) + var proxyConfiguration: ProxyConfiguration + let proxyRelayHops: [ProxyRelayHop] = [relayHop1, relayHop2].filter({ $0 != nil }).map({ $0! }) + if !proxyRelayHops.isEmpty { + proxyConfiguration = ProxyConfiguration(relayHops: proxyRelayHops.compactMap({ $0.toRelayHop() })) + } else { + proxyConfiguration = endpointUrl.scheme?.lowercased() == "socks5" ? + ProxyConfiguration(socksv5Proxy: endpoint) : + ProxyConfiguration(httpCONNECTProxy: endpoint, tlsOptions: endpointUrl.scheme?.lowercased() == "https" ? .init() : nil) + } + + if let allowFailover = allowFailover { + proxyConfiguration.allowFailover = allowFailover + } + if let excludedDomains = excludedDomains { + proxyConfiguration.excludedDomains = excludedDomains + } + if let matchDomains = matchDomains { + proxyConfiguration.matchDomains = matchDomains + } + if let username = username, let password = password { + proxyConfiguration.applyCredential(username: username, password: password) + } + return proxyConfiguration + } +} + +@available(macOS 14.0, *) +public class ProxyRelayHop { + var http3RelayEndpoint: String? + var http2RelayEndpoint: String? + var additionalHTTPHeaders: [String:String]? + + init( + http3RelayEndpoint: String, + http2RelayEndpoint: String?, + additionalHTTPHeaders: [String:String]? + ) { + self.http3RelayEndpoint = http3RelayEndpoint + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + init( + http2RelayEndpoint: String, + additionalHTTPHeaders: [String:String]? + ) { + self.http2RelayEndpoint = http2RelayEndpoint + self.additionalHTTPHeaders = additionalHTTPHeaders + } + + public static func fromMap(map: [String:Any?]?) -> ProxyRelayHop? { + guard let map = map else { + return nil + } + let http3RelayEndpoint = map["http3RelayEndpoint"] as? String + let http2RelayEndpoint = map["http2RelayEndpoint"] as? String + let additionalHTTPHeaders = map["additionalHTTPHeaders"] as? [String:String] + if http3RelayEndpoint == nil, http2RelayEndpoint == nil { + return nil + } + if http3RelayEndpoint != nil { + return ProxyRelayHop( + http3RelayEndpoint: http3RelayEndpoint!, + http2RelayEndpoint: http2RelayEndpoint, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + return ProxyRelayHop( + http2RelayEndpoint: http2RelayEndpoint!, + additionalHTTPHeaders: additionalHTTPHeaders + ) + } + + public func toRelayHop() -> ProxyConfiguration.RelayHop? { + if let http3RelayEndpoint = http3RelayEndpoint, + let url = URL(string: http3RelayEndpoint) { + var http2Endpoint: NWEndpoint? = nil + if let http2RelayEndpoint = http2RelayEndpoint, + let url2 = URL(string: http2RelayEndpoint) { + http2Endpoint = NWEndpoint.url(url2) + } + return ProxyConfiguration.RelayHop(http3RelayEndpoint: NWEndpoint.url(url), + http2RelayEndpoint: http2Endpoint, + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + if let http2RelayEndpoint = http2RelayEndpoint, + let url = URL(string: http2RelayEndpoint) { + return ProxyConfiguration.RelayHop(http2RelayEndpoint: NWEndpoint.url(url), + additionalHTTPHeaderFields: additionalHTTPHeaders ?? [:]) + } + return nil + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift b/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift new file mode 100644 index 000000000..eb5a2c973 --- /dev/null +++ b/flutter_inappwebview_macos/macos/Classes/Types/JavaScriptHandlerFunctionData.swift @@ -0,0 +1,41 @@ +// +// JavaScriptHandlerFunctionData.swift +// Pods +// +// Created by Lorenzo Pichilli on 27/10/24. +// +public class JavaScriptHandlerFunctionData: NSObject { + var args: String + var isMainFrame: Bool + var origin: String + var requestUrl: String + + public init(args: String, isMainFrame: Bool, origin: String, requestUrl: String) { + self.args = args + self.isMainFrame = isMainFrame + self.origin = origin + self.requestUrl = requestUrl + } + + public static func fromMap(map: [String:Any?]?) -> JavaScriptHandlerFunctionData? { + guard let map = map else { + return nil + } + + return JavaScriptHandlerFunctionData( + args: map["args"] as! String, + isMainFrame: map["isMainFrame"] as! Bool, + origin: map["origin"] as! String, + requestUrl: map["requestUrl"] as! String + ) + } + + public func toMap () -> [String:Any?] { + return [ + "args": args, + "isMainFrame": isMainFrame, + "origin": origin, + "requestUrl": requestUrl + ] + } +} diff --git a/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift b/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift index 7218099b6..096f5d883 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/PluginScript.swift @@ -16,8 +16,8 @@ public class PluginScript: UserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -36,8 +36,9 @@ public class PluginScript: UserScript { } @available(macOS 11.0, *) - public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { - super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, + allowedOriginRules: [String]?, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) { + super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld, allowedOriginRules: allowedOriginRules) self.requiredInAllContentWorlds = requiredInAllContentWorlds self.messageHandlerNames = messageHandlerNames } @@ -47,6 +48,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, requiredInAllContentWorlds: Bool? = nil, + allowedOriginRules: [String]? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { if #available(macOS 11.0, *) { return PluginScript( @@ -55,6 +57,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -64,6 +67,7 @@ public class PluginScript: UserScript { source: source ?? self.source, injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -75,6 +79,7 @@ public class PluginScript: UserScript { injectionTime: WKUserScriptInjectionTime? = nil, forMainFrameOnly: Bool? = nil, contentWorld: WKContentWorld? = nil, + allowedOriginRules: [String]? = nil, requiredInAllContentWorlds: Bool? = nil, messageHandlerNames: [String]? = nil) -> PluginScript { return PluginScript( @@ -83,6 +88,7 @@ public class PluginScript: UserScript { injectionTime: injectionTime ?? self.injectionTime, forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly, in: contentWorld ?? self.contentWorld, + allowedOriginRules: allowedOriginRules ?? self.allowedOriginRules, requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds, messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames ) @@ -95,6 +101,7 @@ public class PluginScript: UserScript { lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && lhs.contentWorld == rhs.contentWorld && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } else { @@ -102,6 +109,7 @@ public class PluginScript: UserScript { lhs.source == rhs.source && lhs.injectionTime == rhs.injectionTime && lhs.isForMainFrameOnly == rhs.isForMainFrameOnly && + lhs.allowedOriginRules == rhs.allowedOriginRules && lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds && lhs.messageHandlerNames == rhs.messageHandlerNames } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift b/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift index d350836e3..cd8559a1a 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/UserScript.swift @@ -10,6 +10,7 @@ import WebKit public class UserScript: WKUserScript { var groupName: String? + var allowedOriginRules: [String]? private var contentWorldWrapper: Any? @available(macOS 11.0, *) @@ -27,9 +28,10 @@ public class UserScript: WKUserScript { super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) } - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly) self.groupName = groupName + self.allowedOriginRules = allowedOriginRules } @available(macOS 11.0, *) @@ -39,10 +41,34 @@ public class UserScript: WKUserScript { } @available(macOS 11.0, *) - public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld) { - super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) + public init(groupName: String?, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, allowedOriginRules: [String]?) { + super.init(source: UserScript.wrapSourceCodeAddChecks(source: source, allowedOriginRules: allowedOriginRules), injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld) self.groupName = groupName self.contentWorld = contentWorld + self.allowedOriginRules = allowedOriginRules + } + + static private func wrapSourceCodeAddChecks(source: String, allowedOriginRules: [String]?) -> String { + var ifStatement = "if (" + if let allowedOriginRules = allowedOriginRules, !allowedOriginRules.contains("*") { + if allowedOriginRules.isEmpty { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return "" + } + var jsRegExpArray = "[" + for allowedOriginRule in allowedOriginRules { + if jsRegExpArray.count > 1 { + jsRegExpArray += "," + } + jsRegExpArray += "new RegExp('\(allowedOriginRule.replacingOccurrences(of: "\'", with: "\\'"))')" + } + if jsRegExpArray.count > 1 { + jsRegExpArray += "]" + ifStatement += "\(jsRegExpArray).some(function(rx) { return rx.test(window.location.origin); })" + } + } + return ifStatement.count > 4 ? "\(ifStatement)) { \(source) }" : source } public static func fromMap(map: [String:Any?]?, windowId: Int64?) -> UserScript? { @@ -58,14 +84,16 @@ public class UserScript: WKUserScript { source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, forMainFrameOnly: map["forMainFrameOnly"] as! Bool, - in: contentWorld + in: contentWorld, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } return UserScript( groupName: map["groupName"] as? String, source: map["source"] as! String, injectionTime: WKUserScriptInjectionTime.init(rawValue: map["injectionTime"] as! Int) ?? .atDocumentStart, - forMainFrameOnly: map["forMainFrameOnly"] as! Bool + forMainFrameOnly: map["forMainFrameOnly"] as! Bool, + allowedOriginRules: map["allowedOriginRules"] as? [String] ) } } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift b/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift index 21d0b62cd..ba0f49e8b 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/WKUserContentController.swift @@ -166,7 +166,7 @@ extension WKUserContentController { } } if let windowId = contentWorld.windowId { - generatedCode += "\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(String(windowId));\n" + generatedCode += "\(WindowIdJS.WINDOW_ID_VARIABLE_JS_SOURCE()) = \(String(windowId));\n" } return generatedCode + "\n" + source } diff --git a/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift b/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift index b7a6e53ca..7cb2f3b58 100644 --- a/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift +++ b/flutter_inappwebview_macos/macos/Classes/Types/WebMessagePort.swift @@ -33,10 +33,10 @@ public class WebMessagePort: NSObject { let index = name == "port1" ? 0 : 1 webView.evaluateJavascript(source: """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).onmessage = function (event) { - window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ + window.\(JavaScriptBridgeJS.get_JAVASCRIPT_BRIDGE_NAME()).callHandler('onWebMessagePortMessageReceived', { "webMessageChannelId": "\(webMessageChannel.id)", "index": \(String(index)), "message": { @@ -74,14 +74,14 @@ public class WebMessagePort: NSObject { throw NSError(domain: "Port is already closed or transferred", code: 0) } port.isTransferred = true - portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)") + portArrayString.append("\(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())['\(port.webMessageChannel!.id)'].\(port.name)") } portsString = "[" + portArrayString.joined(separator: ", ") + "]" } let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString)); } @@ -104,7 +104,7 @@ public class WebMessagePort: NSObject { if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView { let source = """ (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; + var webMessageChannel = \(WebMessageChannelJS.WEB_MESSAGE_CHANNELS_VARIABLE_NAME())["\(webMessageChannel.id)"]; if (webMessageChannel != null) { webMessageChannel.\(self.name).close(); } diff --git a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index 92140fe1e..c314423bf 100644 --- a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -34,7 +34,7 @@ public class WebAuthenticationSession: NSObject, ASWebAuthenticationPresentation self.session = session } let channel = FlutterMethodChannel(name: WebAuthenticationSession.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: plugin.registrar!.messenger) + binaryMessenger: plugin.registrar.messenger) self.channelDelegate = WebAuthenticationSessionChannelDelegate(webAuthenticationSession: self, channel: channel) } diff --git a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 102438fff..fb153f84a 100644 --- a/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/flutter_inappwebview_macos/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -18,7 +18,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar.messenger)) self.plugin = plugin } diff --git a/flutter_inappwebview_macos/pubspec.yaml b/flutter_inappwebview_macos/pubspec.yaml index 0c7f6d6be..8688fea92 100644 --- a/flutter_inappwebview_macos/pubspec.yaml +++ b/flutter_inappwebview_macos/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_macos description: macOS implementation of the flutter_inappwebview plugin. -version: 1.1.2 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_macos issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,7 +20,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.3.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_platform_interface/CHANGELOG.md b/flutter_inappwebview_platform_interface/CHANGELOG.md index 1ff5e50ac..d53864cad 100644 --- a/flutter_inappwebview_platform_interface/CHANGELOG.md +++ b/flutter_inappwebview_platform_interface/CHANGELOG.md @@ -1,3 +1,37 @@ +## 1.4.0-beta.3 + +- Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` + +## 1.4.0-beta.2 + +- Updated `flutter_inappwebview_internal_annotations` dependency from `^1.1.1` to `^1.2.0` +- Updated `fromMap` static method and `toMap` method implementations +- Updated all WebView events with return type `Future` to type `FutureOr` in order to not force the usage of `async` keyword +- Added `byName`, `name`, `asNameMap` custom enum classes methods +- Added `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled`, `alpha`, `isUserInteractionEnabled`, `handleAcceleratorKeyPressed` properties to `InAppWebViewSettings` +- Added `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` methods to `PlatformWebViewEnvironment` class +- Added `isInterfaceSupported`, `setInputMethodEnabled`, `hideInputMethod`, `showInputMethod` methods to `PlatformInAppWebViewController` class +- Added `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties to `WebViewEnvironmentSettings` +- Added `onDownloadStarting` WebView event and deprecated `onDownloadStartRequest` event +- Added `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` events to `PlatformWebViewEnvironment` class +- Added `onAcceleratorKeyPressed` WebView event +- Fixed missing PrintJobOrientation android values + +## 1.4.0-beta.1 + +- Updated static `fromMap` implementation for some classes +- Updated `kJavaScriptHandlerForbiddenNames` list +- Added `PlatformInAppLocalhostServer.onData` parameter to set a custom on data server callback +- Added `javaScriptBridgeEnabled`, `javaScriptBridgeOriginAllowList`, `javaScriptBridgeForMainFrameOnly`, `pluginScriptsOriginAllowList`, `pluginScriptsForMainFrameOnly`, `javaScriptHandlersOriginAllowList`, `javaScriptHandlersForMainFrameOnly`, `scrollMultiplier` InAppWebViewSettings parameters +- Added `setJavaScriptBridgeName`, `getJavaScriptBridgeName` static WebView controller methods +- Added `onProcessFailed` WebView event +- Added `regexToAllowSyncUrlLoading` Android-specific property to `InAppWebViewSettings` +- Added `JavaScriptHandlerFunctionData` type +- Deprecated `JavaScriptHandlerCallback` type in favor of `JavaScriptHandlerFunction` type +- Deprecated `InAppWebViewSettings.forceDark` and `InAppWebViewSettings.forceDarkStrategy` Android-only properties in favor of `InAppWebViewSettings.algorithmicDarkeningAllowed` +- Fixed X509Certificate PEM base64 decoding + ## 1.3.0+1 - Fixed `X509Certificate.toMap` method diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart index 400296a19..8ed203e92 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.dart @@ -5,6 +5,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'platform_chrome_safari_browser.dart'; import 'chrome_safari_browser_menu_item.dart'; import '../web_uri.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_action_button.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart index 5080fec54..9a2ab89d6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_action_button.g.dart @@ -46,7 +46,8 @@ class ChromeSafariBrowserActionButton { this.shouldTint = false}); ///Gets a possible [ChromeSafariBrowserActionButton] instance from a [Map] value. - static ChromeSafariBrowserActionButton? fromMap(Map? map) { + static ChromeSafariBrowserActionButton? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -55,12 +56,14 @@ class ChromeSafariBrowserActionButton { icon: Uint8List.fromList(map['icon'].cast()), id: map['id'], ); - instance.shouldTint = map['shouldTint']; + if (map['shouldTint'] != null) { + instance.shouldTint = map['shouldTint']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "description": description, "icon": icon, diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart index f3a625798..e0a8c361d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../types/ui_image.dart'; import 'platform_chrome_safari_browser.dart'; import '../web_uri.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_menu_item.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart index 9bb9f017a..ec9618d04 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_menu_item.g.dart @@ -44,23 +44,25 @@ class ChromeSafariBrowserMenuItem { this.onClick}); ///Gets a possible [ChromeSafariBrowserMenuItem] instance from a [Map] value. - static ChromeSafariBrowserMenuItem? fromMap(Map? map) { + static ChromeSafariBrowserMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserMenuItem( id: map['id'], - image: UIImage.fromMap(map['image']?.cast()), + image: UIImage.fromMap(map['image']?.cast(), + enumMethod: enumMethod), label: map['label'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, - "image": image?.toMap(), + "image": image?.toMap(enumMethod: enumMethod), "label": label, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart index 6404b07a9..5da88d85e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../types/android_resource.dart'; import '../web_uri.dart'; import 'platform_chrome_safari_browser.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_secondary_toolbar.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart index 7b1a7a3e8..3b4a72c59 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_secondary_toolbar.g.dart @@ -35,27 +35,32 @@ class ChromeSafariBrowserSecondaryToolbar { {this.clickableIDs = const [], required this.layout}); ///Gets a possible [ChromeSafariBrowserSecondaryToolbar] instance from a [Map] value. - static ChromeSafariBrowserSecondaryToolbar? fromMap( - Map? map) { + static ChromeSafariBrowserSecondaryToolbar? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSecondaryToolbar( - layout: AndroidResource.fromMap(map['layout']?.cast())!, + layout: AndroidResource.fromMap(map['layout']?.cast(), + enumMethod: enumMethod)!, ); - instance.clickableIDs = - List.from( - map['clickableIDs'].map((e) => - ChromeSafariBrowserSecondaryToolbarClickableID.fromMap( - e?.cast())!)); + if (map['clickableIDs'] != null) { + instance.clickableIDs = + List.from( + map['clickableIDs'].map((e) => + ChromeSafariBrowserSecondaryToolbarClickableID.fromMap( + e?.cast(), + enumMethod: enumMethod)!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "clickableIDs": clickableIDs.map((e) => e.toMap()).toList(), - "layout": layout.toMap(), + "clickableIDs": + clickableIDs.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "layout": layout.toMap(enumMethod: enumMethod), }; } @@ -93,20 +98,22 @@ class ChromeSafariBrowserSecondaryToolbarClickableID { ///Gets a possible [ChromeSafariBrowserSecondaryToolbarClickableID] instance from a [Map] value. static ChromeSafariBrowserSecondaryToolbarClickableID? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSecondaryToolbarClickableID( - id: AndroidResource.fromMap(map['id']?.cast())!, + id: AndroidResource.fromMap(map['id']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "id": id.toMap(), + "id": id.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart index b8d6610ce..7d854d45b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.dart @@ -15,17 +15,20 @@ import '../types/ui_event_attribution.dart'; import '../util.dart'; import 'android/chrome_custom_tabs_options.dart'; import 'apple/safari_options.dart'; +import '../types/enum_method.dart'; part 'chrome_safari_browser_settings.g.dart'; TrustedWebActivityDisplayMode? _deserializeDisplayMode( - Map? displayMode) { + Map? displayMode, + {EnumMethod? enumMethod}) { if (displayMode == null) { return null; } switch (displayMode["type"]) { case "IMMERSIVE_MODE": - return TrustedWebActivityImmersiveDisplayMode.fromMap(displayMode); + return TrustedWebActivityImmersiveDisplayMode.fromMap(displayMode, + enumMethod: enumMethod); case "DEFAULT_MODE": default: return TrustedWebActivityDefaultDisplayMode(); diff --git a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart index 4ec954015..577c4fac2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/chrome_safari_browser/chrome_safari_browser_settings.g.dart @@ -252,19 +252,24 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { } ///Gets a possible [ChromeSafariBrowserSettings] instance from a [Map] value. - static ChromeSafariBrowserSettings? fromMap(Map? map) { + static ChromeSafariBrowserSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ChromeSafariBrowserSettings( activityButton: ActivityButton.fromMap( - map['activityButton']?.cast()), - displayMode: _deserializeDisplayMode(map['displayMode']), + map['activityButton']?.cast(), + enumMethod: enumMethod), + displayMode: + _deserializeDisplayMode(map['displayMode'], enumMethod: enumMethod), eventAttribution: UIEventAttribution.fromMap( - map['eventAttribution']?.cast()), + map['eventAttribution']?.cast(), + enumMethod: enumMethod), exitAnimations: map['exitAnimations'] != null - ? List.from(map['exitAnimations'] - .map((e) => AndroidResource.fromMap(e?.cast())!)) + ? List.from(map['exitAnimations'].map((e) => + AndroidResource.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, navigationBarColor: map['navigationBarColor'] != null ? UtilColor.fromStringRepresentation(map['navigationBarColor']) @@ -283,8 +288,9 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { ? UtilColor.fromStringRepresentation(map['secondaryToolbarColor']) : null, startAnimations: map['startAnimations'] != null - ? List.from(map['startAnimations'] - .map((e) => AndroidResource.fromMap(e?.cast())!)) + ? List.from(map['startAnimations'].map((e) => + AndroidResource.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, toolbarBackgroundColor: map['toolbarBackgroundColor'] != null ? UtilColor.fromStringRepresentation(map['toolbarBackgroundColor']) @@ -296,7 +302,13 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { instance.alwaysUseBrowserUI = map['alwaysUseBrowserUI']; instance.barCollapsingEnabled = map['barCollapsingEnabled']; instance.dismissButtonStyle = - DismissButtonStyle.fromNativeValue(map['dismissButtonStyle']); + switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + DismissButtonStyle.fromNativeValue(map['dismissButtonStyle']), + EnumMethod.value => + DismissButtonStyle.fromValue(map['dismissButtonStyle']), + EnumMethod.name => DismissButtonStyle.byName(map['dismissButtonStyle']) + }; instance.enableUrlBarHiding = map['enableUrlBarHiding']; instance.entersReaderIfAvailable = map['entersReaderIfAvailable']; instance.instantAppsEnabled = map['instantAppsEnabled']; @@ -304,32 +316,57 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { instance.isTrustedWebActivity = map['isTrustedWebActivity']; instance.keepAliveEnabled = map['keepAliveEnabled']; instance.noHistory = map['noHistory']; - instance.presentationStyle = - ModalPresentationStyle.fromNativeValue(map['presentationStyle']); - instance.screenOrientation = + instance.presentationStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalPresentationStyle.fromNativeValue(map['presentationStyle']), + EnumMethod.value => + ModalPresentationStyle.fromValue(map['presentationStyle']), + EnumMethod.name => ModalPresentationStyle.byName(map['presentationStyle']) + }; + instance.screenOrientation = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => TrustedWebActivityScreenOrientation.fromNativeValue( - map['screenOrientation']); - instance.shareState = - CustomTabsShareState.fromNativeValue(map['shareState']); + map['screenOrientation']), + EnumMethod.value => + TrustedWebActivityScreenOrientation.fromValue(map['screenOrientation']), + EnumMethod.name => + TrustedWebActivityScreenOrientation.byName(map['screenOrientation']) + }; + instance.shareState = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CustomTabsShareState.fromNativeValue(map['shareState']), + EnumMethod.value => CustomTabsShareState.fromValue(map['shareState']), + EnumMethod.name => CustomTabsShareState.byName(map['shareState']) + }; instance.showTitle = map['showTitle']; - instance.transitionStyle = - ModalTransitionStyle.fromNativeValue(map['transitionStyle']); + instance.transitionStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalTransitionStyle.fromNativeValue(map['transitionStyle']), + EnumMethod.value => + ModalTransitionStyle.fromValue(map['transitionStyle']), + EnumMethod.name => ModalTransitionStyle.byName(map['transitionStyle']) + }; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "activityButton": activityButton?.toMap(), + "activityButton": activityButton?.toMap(enumMethod: enumMethod), "additionalTrustedOrigins": additionalTrustedOrigins, "alwaysUseBrowserUI": alwaysUseBrowserUI, "barCollapsingEnabled": barCollapsingEnabled, - "dismissButtonStyle": dismissButtonStyle?.toNativeValue(), - "displayMode": displayMode?.toMap(), + "dismissButtonStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => dismissButtonStyle?.toNativeValue(), + EnumMethod.value => dismissButtonStyle?.toValue(), + EnumMethod.name => dismissButtonStyle?.name() + }, + "displayMode": displayMode?.toMap(enumMethod: enumMethod), "enableUrlBarHiding": enableUrlBarHiding, "entersReaderIfAvailable": entersReaderIfAvailable, - "eventAttribution": eventAttribution?.toMap(), - "exitAnimations": exitAnimations?.map((e) => e.toMap()).toList(), + "eventAttribution": eventAttribution?.toMap(enumMethod: enumMethod), + "exitAnimations": + exitAnimations?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "instantAppsEnabled": instantAppsEnabled, "isSingleInstance": isSingleInstance, "isTrustedWebActivity": isTrustedWebActivity, @@ -340,14 +377,31 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions { "packageName": packageName, "preferredBarTintColor": preferredBarTintColor?.toHex(), "preferredControlTintColor": preferredControlTintColor?.toHex(), - "presentationStyle": presentationStyle?.toNativeValue(), - "screenOrientation": screenOrientation?.toNativeValue(), + "presentationStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => presentationStyle?.toNativeValue(), + EnumMethod.value => presentationStyle?.toValue(), + EnumMethod.name => presentationStyle?.name() + }, + "screenOrientation": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => screenOrientation?.toNativeValue(), + EnumMethod.value => screenOrientation?.toValue(), + EnumMethod.name => screenOrientation?.name() + }, "secondaryToolbarColor": secondaryToolbarColor?.toHex(), - "shareState": shareState?.toNativeValue(), + "shareState": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => shareState?.toNativeValue(), + EnumMethod.value => shareState?.toValue(), + EnumMethod.name => shareState?.name() + }, "showTitle": showTitle, - "startAnimations": startAnimations?.map((e) => e.toMap()).toList(), + "startAnimations": + startAnimations?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "toolbarBackgroundColor": toolbarBackgroundColor?.toHex(), - "transitionStyle": transitionStyle?.toNativeValue(), + "transitionStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => transitionStyle?.toNativeValue(), + EnumMethod.value => transitionStyle?.toValue(), + EnumMethod.name => transitionStyle?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart b/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart index 6d6a6eb71..5df73b868 100755 --- a/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart +++ b/flutter_inappwebview_platform_interface/lib/src/content_blocker.dart @@ -16,16 +16,22 @@ class ContentBlocker { ContentBlocker({required this.trigger, required this.action}); - Map> toMap() { - return {"trigger": trigger.toMap(), "action": action.toMap()}; + Map> toMap({EnumMethod? enumMethod}) { + return { + "trigger": trigger.toMap(enumMethod: enumMethod), + "action": action.toMap(enumMethod: enumMethod) + }; } - static ContentBlocker fromMap(Map> map) { + static ContentBlocker fromMap(Map> map, + {EnumMethod? enumMethod}) { return ContentBlocker( trigger: ContentBlockerTrigger.fromMap( - Map.from(map["trigger"]!)), + Map.from(map["trigger"]!), + enumMethod: enumMethod), action: ContentBlockerAction.fromMap( - Map.from(map["action"]!))); + Map.from(map["action"]!), + enumMethod: enumMethod)); } @override @@ -138,18 +144,30 @@ class ContentBlockerTrigger { assert(!(this.ifTopUrl.isEmpty || this.unlessTopUrl.isEmpty) == false); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { List resourceTypeStringList = []; resourceType.forEach((type) { - resourceTypeStringList.add(type.toNativeValue()); + resourceTypeStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); List loadTypeStringList = []; loadType.forEach((type) { - loadTypeStringList.add(type.toNativeValue()); + loadTypeStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); List loadContextStringList = []; loadContext.forEach((type) { - loadContextStringList.add(type.toNativeValue()); + loadContextStringList.add(switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }); }); Map map = { @@ -175,7 +193,8 @@ class ContentBlockerTrigger { return map; } - static ContentBlockerTrigger fromMap(Map map) { + static ContentBlockerTrigger fromMap(Map map, + {EnumMethod? enumMethod}) { List resourceType = []; List loadType = []; List loadContext = []; @@ -183,7 +202,13 @@ class ContentBlockerTrigger { List resourceTypeStringList = List.from(map["resource-type"] ?? []); resourceTypeStringList.forEach((typeValue) { - var type = ContentBlockerTriggerResourceType.fromNativeValue(typeValue); + var type = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerResourceType.fromNativeValue(typeValue), + EnumMethod.value => + ContentBlockerTriggerResourceType.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerResourceType.byName(typeValue), + }; if (type != null) { resourceType.add(type); } @@ -191,7 +216,12 @@ class ContentBlockerTrigger { List loadTypeStringList = List.from(map["load-type"] ?? []); loadTypeStringList.forEach((typeValue) { - var type = ContentBlockerTriggerLoadType.fromNativeValue(typeValue); + var type = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerLoadType.fromNativeValue(typeValue), + EnumMethod.value => ContentBlockerTriggerLoadType.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerLoadType.byName(typeValue), + }; if (type != null) { loadType.add(type); } @@ -200,7 +230,13 @@ class ContentBlockerTrigger { List loadContextStringList = List.from(map["load-context"] ?? []); loadContextStringList.forEach((typeValue) { - var context = ContentBlockerTriggerLoadContext.fromNativeValue(typeValue); + var context = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerTriggerLoadContext.fromNativeValue(typeValue), + EnumMethod.value => + ContentBlockerTriggerLoadContext.fromValue(typeValue), + EnumMethod.name => ContentBlockerTriggerLoadContext.byName(typeValue), + }; if (context != null) { loadContext.add(context); } @@ -244,9 +280,13 @@ class ContentBlockerAction { } } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { Map map = { - "type": type.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, "selector": selector }; @@ -260,9 +300,15 @@ class ContentBlockerAction { return map; } - static ContentBlockerAction fromMap(Map map) { + static ContentBlockerAction fromMap(Map map, + {EnumMethod? enumMethod}) { return ContentBlockerAction( - type: ContentBlockerActionType.fromNativeValue(map["type"])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ContentBlockerActionType.fromNativeValue(map["type"]), + EnumMethod.value => ContentBlockerActionType.fromValue(map["type"]), + EnumMethod.name => ContentBlockerActionType.byName(map["type"]) + }!, selector: map["selector"]); } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart index 6e790c4d7..44a3740de 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.dart @@ -4,6 +4,7 @@ import '../in_app_webview/platform_webview.dart'; import '../types/in_app_webview_hit_test_result.dart'; import 'context_menu_item.dart'; import 'context_menu_settings.dart'; +import '../types/enum_method.dart'; part 'context_menu.g.dart'; @@ -33,7 +34,7 @@ class ContextMenu_ { ///Use [settings] instead @Deprecated("Use settings instead") - final ContextMenuOptions? options; + final ContextMenuOptions_? options; ///Context menu settings. final ContextMenuSettings_? settings; @@ -52,10 +53,11 @@ class ContextMenu_ { @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return { "settings": - (settings as ContextMenuSettings?)?.toMap() ?? options?.toMap() + (settings as ContextMenuSettings?)?.toMap(enumMethod: enumMethod) ?? + (options as ContextMenuOptions?)?.toMap(enumMethod: enumMethod) }; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart index 84e63405f..5bb9e004c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu.g.dart @@ -52,34 +52,41 @@ class ContextMenu { this.onContextMenuActionItemClicked}); ///Gets a possible [ContextMenu] instance from a [Map] value. - static ContextMenu? fromMap(Map? map) { + static ContextMenu? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ContextMenu( - menuItems: List.from(map['menuItems'] - .map((e) => ContextMenuItem.fromMap(e?.cast())!)), - options: map['settings'], - settings: - ContextMenuSettings.fromMap(map['settings']?.cast()), + menuItems: List.from(map['menuItems'].map((e) => + ContextMenuItem.fromMap(e?.cast(), + enumMethod: enumMethod)!)), + options: ContextMenuOptions.fromMap( + map['settings']?.cast(), + enumMethod: enumMethod), + settings: ContextMenuSettings.fromMap( + map['settings']?.cast(), + enumMethod: enumMethod), ); return instance; } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return { "settings": - (settings as ContextMenuSettings?)?.toMap() ?? options?.toMap() + (settings as ContextMenuSettings?)?.toMap(enumMethod: enumMethod) ?? + (options as ContextMenuOptions?)?.toMap(enumMethod: enumMethod) }; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "menuItems": menuItems.map((e) => e.toMap()).toList(), - "settings": settings?.toMap(), - ..._toMapMergeWith(), + "menuItems": + menuItems.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "settings": settings?.toMap(enumMethod: enumMethod), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart index ec7a29bcd..355b093d4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'context_menu.dart'; import '../util.dart'; +import '../types/enum_method.dart'; part 'context_menu_item.g.dart'; @@ -47,7 +48,7 @@ class ContextMenuItem_ { @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"androidId": androidId, "iosId": iosId}; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart index 242c1f2b5..f40250cda 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_item.g.dart @@ -42,7 +42,8 @@ class ContextMenuItem { } ///Gets a possible [ContextMenuItem] instance from a [Map] value. - static ContextMenuItem? fromMap(Map? map) { + static ContextMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -56,16 +57,16 @@ class ContextMenuItem { } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"androidId": androidId, "iosId": iosId}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, "title": title, - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart index dd6c001a4..f90b3409f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'context_menu.dart'; +import '../types/enum_method.dart'; part 'context_menu_settings.g.dart'; @@ -15,24 +16,10 @@ class ContextMenuSettings_ { ///Use [ContextMenuSettings] instead. @Deprecated("Use ContextMenuSettings instead") -class ContextMenuOptions { +@ExchangeableObject(copyMethod: true) +class ContextMenuOptions_ { ///Whether all the default system context menu items should be hidden or not. The default value is `false`. bool hideDefaultSystemContextMenuItems; - ContextMenuOptions({this.hideDefaultSystemContextMenuItems = false}); - - Map toMap() { - return { - "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } + ContextMenuOptions_({this.hideDefaultSystemContextMenuItems = false}); } diff --git a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart index 046460726..6713d0a16 100644 --- a/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/context_menu/context_menu_settings.g.dart @@ -13,18 +13,21 @@ class ContextMenuSettings { ContextMenuSettings({this.hideDefaultSystemContextMenuItems = false}); ///Gets a possible [ContextMenuSettings] instance from a [Map] value. - static ContextMenuSettings? fromMap(Map? map) { + static ContextMenuSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ContextMenuSettings(); - instance.hideDefaultSystemContextMenuItems = - map['hideDefaultSystemContextMenuItems']; + if (map['hideDefaultSystemContextMenuItems'] != null) { + instance.hideDefaultSystemContextMenuItems = + map['hideDefaultSystemContextMenuItems']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems, }; @@ -45,3 +48,47 @@ class ContextMenuSettings { return 'ContextMenuSettings{hideDefaultSystemContextMenuItems: $hideDefaultSystemContextMenuItems}'; } } + +///Use [ContextMenuSettings] instead. +@Deprecated('Use ContextMenuSettings instead') +class ContextMenuOptions { + ///Whether all the default system context menu items should be hidden or not. The default value is `false`. + bool hideDefaultSystemContextMenuItems; + ContextMenuOptions({this.hideDefaultSystemContextMenuItems = false}); + + ///Gets a possible [ContextMenuOptions] instance from a [Map] value. + static ContextMenuOptions? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ContextMenuOptions(); + if (map['hideDefaultSystemContextMenuItems'] != null) { + instance.hideDefaultSystemContextMenuItems = + map['hideDefaultSystemContextMenuItems']; + } + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "hideDefaultSystemContextMenuItems": hideDefaultSystemContextMenuItems, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + ///Returns a copy of ContextMenuOptions. + ContextMenuOptions copy() { + return ContextMenuOptions.fromMap(toMap()) ?? ContextMenuOptions(); + } + + @override + String toString() { + return 'ContextMenuOptions{hideDefaultSystemContextMenuItems: $hideDefaultSystemContextMenuItems}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart index 5f082e7ae..b14ab4a09 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.dart @@ -7,24 +7,25 @@ import '../util.dart'; import 'platform_in_app_browser.dart'; import '../types/main.dart'; +import '../types/enum_method.dart'; part 'in_app_browser_menu_item.g.dart'; -dynamic _serializeIcon(dynamic icon) { - return icon is Uint8List ? icon : icon?.toMap(); +dynamic _serializeIcon(dynamic icon, {EnumMethod? enumMethod}) { + return icon is Uint8List ? icon : icon?.toMap(enumMethod: enumMethod); } -dynamic _deserializeIcon(dynamic icon) { +dynamic _deserializeIcon(dynamic icon, {EnumMethod? enumMethod}) { if (icon is Uint8List) { return icon; } if (icon is Map) { final iconMap = icon as Map; if (iconMap.containsKey('defType')) { - return AndroidResource.fromMap(iconMap); + return AndroidResource.fromMap(iconMap, enumMethod: enumMethod); } if (iconMap.containsKey('systemName')) { - return UIImage.fromMap(iconMap); + return UIImage.fromMap(iconMap, enumMethod: enumMethod); } } return null; diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart index 90620b746..5046e5d82 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_menu_item.g.dart @@ -54,12 +54,13 @@ class InAppBrowserMenuItem { required this.title}); ///Gets a possible [InAppBrowserMenuItem] instance from a [Map] value. - static InAppBrowserMenuItem? fromMap(Map? map) { + static InAppBrowserMenuItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = InAppBrowserMenuItem( - icon: _deserializeIcon(map['icon']), + icon: _deserializeIcon(map['icon'], enumMethod: enumMethod), iconColor: map['iconColor'] != null ? UtilColor.fromStringRepresentation(map['iconColor']) : null, @@ -67,14 +68,16 @@ class InAppBrowserMenuItem { order: map['order'], title: map['title'], ); - instance.showAsAction = map['showAsAction']; + if (map['showAsAction'] != null) { + instance.showAsAction = map['showAsAction']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "icon": _serializeIcon(icon), + "icon": _serializeIcon(icon, enumMethod: enumMethod), "iconColor": iconColor?.toHex(), "id": id, "order": order, diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart index f63b06ce1..857cce41c 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.dart @@ -17,6 +17,7 @@ import '../in_app_webview/android/in_app_webview_options.dart'; import 'apple/in_app_browser_options.dart'; import '../in_app_webview/apple/in_app_webview_options.dart'; +import '../types/enum_method.dart'; part 'in_app_browser_settings.g.dart'; @@ -53,14 +54,16 @@ class InAppBrowserClassSettings { } factory InAppBrowserClassSettings.fromMap(Map options, - {InAppBrowserClassSettings? instance}) { + {InAppBrowserClassSettings? instance, EnumMethod? enumMethod}) { if (instance == null) { instance = InAppBrowserClassSettings(); } instance.browserSettings = - InAppBrowserSettings.fromMap(options) ?? InAppBrowserSettings(); + InAppBrowserSettings.fromMap(options, enumMethod: enumMethod) ?? + InAppBrowserSettings(); instance.webViewSettings = - InAppWebViewSettings.fromMap(options) ?? InAppWebViewSettings(); + InAppWebViewSettings.fromMap(options, enumMethod: enumMethod) ?? + InAppWebViewSettings(); return instance; } @@ -329,7 +332,8 @@ class InAppBrowserClassOptions { return toMap().toString(); } - static InAppBrowserClassOptions fromMap(Map options) { + static InAppBrowserClassOptions fromMap(Map options, + {EnumMethod? enumMethod}) { InAppBrowserClassOptions inAppBrowserClassOptions = InAppBrowserClassOptions(); diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart index 9f8d8fee7..77422cdaa 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/in_app_browser_settings.g.dart @@ -235,7 +235,8 @@ class InAppBrowserSettings this.windowType}); ///Gets a possible [InAppBrowserSettings] instance from a [Map] value. - static InAppBrowserSettings? fromMap(Map? map) { + static InAppBrowserSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -261,13 +262,29 @@ class InAppBrowserSettings toolbarTopTintColor: map['toolbarTopTintColor'] != null ? UtilColor.fromStringRepresentation(map['toolbarTopTintColor']) : null, - windowFrame: - InAppWebViewRect.fromMap(map['windowFrame']?.cast()), - windowStyleMask: WindowStyleMask.fromNativeValue(map['windowStyleMask']), - windowTitlebarSeparatorStyle: - WindowTitlebarSeparatorStyle.fromNativeValue( - map['windowTitlebarSeparatorStyle']), - windowType: WindowType.fromNativeValue(map['windowType']), + windowFrame: InAppWebViewRect.fromMap( + map['windowFrame']?.cast(), + enumMethod: enumMethod), + windowStyleMask: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WindowStyleMask.fromNativeValue(map['windowStyleMask']), + EnumMethod.value => WindowStyleMask.fromValue(map['windowStyleMask']), + EnumMethod.name => WindowStyleMask.byName(map['windowStyleMask']) + }, + windowTitlebarSeparatorStyle: switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WindowTitlebarSeparatorStyle.fromNativeValue( + map['windowTitlebarSeparatorStyle']), + EnumMethod.value => WindowTitlebarSeparatorStyle.fromValue( + map['windowTitlebarSeparatorStyle']), + EnumMethod.name => WindowTitlebarSeparatorStyle.byName( + map['windowTitlebarSeparatorStyle']) + }, + windowType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WindowType.fromNativeValue(map['windowType']), + EnumMethod.value => WindowType.fromValue(map['windowType']), + EnumMethod.name => WindowType.byName(map['windowType']) + }, ); instance.allowGoBackWithBackButton = map['allowGoBackWithBackButton']; instance.closeOnCannotGoBack = map['closeOnCannotGoBack']; @@ -279,8 +296,13 @@ class InAppBrowserSettings instance.hideToolbarBottom = map['hideToolbarBottom']; instance.hideToolbarTop = map['hideToolbarTop']; instance.hideUrlBar = map['hideUrlBar']; - instance.presentationStyle = - ModalPresentationStyle.fromNativeValue(map['presentationStyle']); + instance.presentationStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalPresentationStyle.fromNativeValue(map['presentationStyle']), + EnumMethod.value => + ModalPresentationStyle.fromValue(map['presentationStyle']), + EnumMethod.name => ModalPresentationStyle.byName(map['presentationStyle']) + }; instance.shouldCloseOnBackButtonPressed = map['shouldCloseOnBackButtonPressed']; instance.toolbarBottomTranslucent = map['toolbarBottomTranslucent']; @@ -288,14 +310,19 @@ class InAppBrowserSettings ? UtilColor.fromStringRepresentation(map['toolbarTopBarTintColor']) : null; instance.toolbarTopTranslucent = map['toolbarTopTranslucent']; - instance.transitionStyle = - ModalTransitionStyle.fromNativeValue(map['transitionStyle']); + instance.transitionStyle = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ModalTransitionStyle.fromNativeValue(map['transitionStyle']), + EnumMethod.value => + ModalTransitionStyle.fromValue(map['transitionStyle']), + EnumMethod.name => ModalTransitionStyle.byName(map['transitionStyle']) + }; instance.windowAlphaValue = map['windowAlphaValue']; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowGoBackWithBackButton": allowGoBackWithBackButton, "closeButtonCaption": closeButtonCaption, @@ -310,7 +337,11 @@ class InAppBrowserSettings "hideToolbarTop": hideToolbarTop, "hideUrlBar": hideUrlBar, "menuButtonColor": menuButtonColor?.toHex(), - "presentationStyle": presentationStyle?.toNativeValue(), + "presentationStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => presentationStyle?.toNativeValue(), + EnumMethod.value => presentationStyle?.toValue(), + EnumMethod.name => presentationStyle?.name() + }, "shouldCloseOnBackButtonPressed": shouldCloseOnBackButtonPressed, "toolbarBottomBackgroundColor": toolbarBottomBackgroundColor?.toHex(), "toolbarBottomTintColor": toolbarBottomTintColor?.toHex(), @@ -320,13 +351,29 @@ class InAppBrowserSettings "toolbarTopFixedTitle": toolbarTopFixedTitle, "toolbarTopTintColor": toolbarTopTintColor?.toHex(), "toolbarTopTranslucent": toolbarTopTranslucent, - "transitionStyle": transitionStyle?.toNativeValue(), + "transitionStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => transitionStyle?.toNativeValue(), + EnumMethod.value => transitionStyle?.toValue(), + EnumMethod.name => transitionStyle?.name() + }, "windowAlphaValue": windowAlphaValue, - "windowFrame": windowFrame?.toMap(), - "windowStyleMask": windowStyleMask?.toNativeValue(), - "windowTitlebarSeparatorStyle": - windowTitlebarSeparatorStyle?.toNativeValue(), - "windowType": windowType?.toNativeValue(), + "windowFrame": windowFrame?.toMap(enumMethod: enumMethod), + "windowStyleMask": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowStyleMask?.toNativeValue(), + EnumMethod.value => windowStyleMask?.toValue(), + EnumMethod.name => windowStyleMask?.name() + }, + "windowTitlebarSeparatorStyle": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowTitlebarSeparatorStyle?.toNativeValue(), + EnumMethod.value => windowTitlebarSeparatorStyle?.toValue(), + EnumMethod.name => windowTitlebarSeparatorStyle?.name() + }, + "windowType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => windowType?.toNativeValue(), + EnumMethod.value => windowType?.toValue(), + EnumMethod.name => windowType?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart index 6c14c7180..a22ae1e3b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart @@ -1,29 +1,25 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview_platform_interface/src/types/disposable.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../context_menu/context_menu.dart'; +import '../debug_logging_settings.dart'; import '../find_interaction/platform_find_interaction_controller.dart'; +import '../in_app_webview/in_app_webview_settings.dart'; +import '../in_app_webview/platform_inappwebview_controller.dart'; import '../inappwebview_platform.dart'; +import '../platform_webview_feature.dart'; +import '../print_job/main.dart'; import '../pull_to_refresh/main.dart'; +import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; import '../types/main.dart'; - -import '../in_app_webview/platform_inappwebview_controller.dart'; -import '../in_app_webview/in_app_webview_settings.dart'; - -import '../print_job/main.dart'; import '../web_uri.dart'; import '../webview_environment/platform_webview_environment.dart'; import 'in_app_browser_menu_item.dart'; import 'in_app_browser_settings.dart'; -import '../debug_logging_settings.dart'; -import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppBrowser]. /// @@ -639,7 +635,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.shouldOverrideUrlLoading](https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview)) - Future? shouldOverrideUrlLoading( + FutureOr? shouldOverrideUrlLoading( NavigationAction navigationAction) { return null; } @@ -668,10 +664,14 @@ abstract class PlatformInAppBrowserEvents { ///- MacOS void onScrollChanged(int x, int y) {} - ///Use [onDownloadStartRequest] instead - @Deprecated('Use onDownloadStartRequest instead') + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') void onDownloadStart(Uri url) {} + ///Use [onDownloadStarting] instead + @Deprecated('Use onDownloadStarting instead') + void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + ///Event fired when `WebView` recognizes a downloadable file. ///To download the file, you can use the [flutter_downloader](https://pub.dev/packages/flutter_downloader) plugin. /// @@ -683,11 +683,15 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebView.setDownloadListener](https://developer.android.com/reference/android/webkit/WebView#setDownloadListener(android.webkit.DownloadListener))) ///- iOS ///- MacOS - void onDownloadStartRequest(DownloadStartRequest downloadStartRequest) {} + ///- Windows ([Official API - ICoreWebView2_4.add_DownloadStarting](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_4?view=webview2-1.0.2849.39#add_downloadstarting)) + FutureOr? onDownloadStarting( + DownloadStartRequest downloadStartRequest) { + return null; + } ///Use [onLoadResourceWithCustomScheme] instead. @Deprecated('Use onLoadResourceWithCustomScheme instead') - Future? onLoadResourceCustomScheme(Uri url) { + FutureOr? onLoadResourceCustomScheme(Uri url) { return null; } @@ -699,7 +703,7 @@ abstract class PlatformInAppBrowserEvents { ///- iOS ([Official API - WKURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkurlschemehandler)) ///- MacOS ([Official API - WKURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkurlschemehandler)) ///- Windows - Future? onLoadResourceWithCustomScheme( + FutureOr? onLoadResourceWithCustomScheme( WebResourceRequest request) { return null; } @@ -737,7 +741,7 @@ abstract class PlatformInAppBrowserEvents { ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview)) ///- Windows ([Official API - ICoreWebView2.add_NewWindowRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_newwindowrequested)) - Future? onCreateWindow(CreateWindowAction createWindowAction) { + FutureOr? onCreateWindow(CreateWindowAction createWindowAction) { return null; } @@ -778,7 +782,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsAlert](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsAlert(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview)) - Future? onJsAlert(JsAlertRequest jsAlertRequest) { + FutureOr? onJsAlert(JsAlertRequest jsAlertRequest) { return null; } @@ -791,7 +795,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsConfirm](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsConfirm(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview)) - Future? onJsConfirm(JsConfirmRequest jsConfirmRequest) { + FutureOr? onJsConfirm(JsConfirmRequest jsConfirmRequest) { return null; } @@ -804,7 +808,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onJsPrompt](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsPrompt(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20android.webkit.JsPromptResult))) ///- iOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview)) ///- MacOS ([Official API - WKUIDelegate.webView](https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview)) - Future? onJsPrompt(JsPromptRequest jsPromptRequest) { + FutureOr? onJsPrompt(JsPromptRequest jsPromptRequest) { return null; } @@ -816,8 +820,9 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedHttpAuthRequest](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView,%20android.webkit.HttpAuthHandler,%20java.lang.String,%20java.lang.String))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedHttpAuthRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_10.add_BasicAuthenticationRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_10?view=webview2-1.0.2849.39#add_basicauthenticationrequested)) + FutureOr? onReceivedHttpAuthRequest( + HttpAuthenticationChallenge challenge) { return null; } @@ -826,12 +831,17 @@ abstract class PlatformInAppBrowserEvents { /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge]. /// + ///**NOTE for iOS and macOS**: to override the certificate verification logic, you have to provide ATS (App Transport Security) exceptions in your iOS/macOS `Info.plist`. + ///See `NSAppTransportSecurity` in the [Information Property List Key Reference](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW1) + ///for details. + /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onReceivedSslError](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%20android.webkit.SslErrorHandler,%20android.net.http.SslError))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedServerTrustAuthRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_14.add_ServerCertificateErrorDetected](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_14?view=webview2-1.0.2792.45#add_servercertificateerrordetected)) + FutureOr? onReceivedServerTrustAuthRequest( + ServerTrustChallenge challenge) { return null; } @@ -846,8 +856,9 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebViewClient.onReceivedClientCertRequest](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedClientCertRequest(android.webkit.WebView,%20android.webkit.ClientCertRequest))) ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview)) - Future? onReceivedClientCertRequest( - URLAuthenticationChallenge challenge) { + ///- Windows ([Official API - ICoreWebView2_5.add_ClientCertificateRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_5?view=webview2-1.0.2849.39#add_clientcertificaterequested)) + FutureOr? onReceivedClientCertRequest( + ClientCertChallenge challenge) { return null; } @@ -858,11 +869,17 @@ abstract class PlatformInAppBrowserEvents { ///Event fired when an `XMLHttpRequest` is sent to a server. ///It gives the host application a chance to take control over the request before sending it. + ///This event is implemented using JavaScript under the hood. + /// + ///Due to the async nature of this event implementation, it will intercept only async `XMLHttpRequest`s ([AjaxRequest.isAsync] with `true`). + ///To be able to intercept sync `XMLHttpRequest`s, use [InAppWebViewSettings.interceptOnlyAsyncAjaxRequests] to `false`. + ///If necessary, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// ///[ajaxRequest] represents the `XMLHttpRequest`. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. @@ -871,18 +888,24 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { + FutureOr? shouldInterceptAjaxRequest(AjaxRequest ajaxRequest) { return null; } ///Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes. ///It gives the host application a chance to abort the request. + ///This event is implemented using JavaScript under the hood. + /// + ///Due to the async nature of this event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// ///[ajaxRequest] represents the [XMLHttpRequest]. /// - ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that - ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code + ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] and [InAppWebViewSettings.useOnAjaxReadyStateChange] settings to `true`. + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///can inject javascript code right after the document element is created but before any other content is loaded, the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. /// @@ -890,17 +913,19 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onAjaxReadyStateChange(AjaxRequest ajaxRequest) { + FutureOr? onAjaxReadyStateChange( + AjaxRequest ajaxRequest) { return null; } ///Event fired as an `XMLHttpRequest` progress. ///It gives the host application a chance to abort the request. + ///This event is implemented using JavaScript under the hood. /// ///[ajaxRequest] represents the [XMLHttpRequest]. /// - ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptAjaxRequest] and [InAppWebViewSettings.useOnAjaxProgress] settings to `true`. + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept ajax requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the ajax requests will be intercept for sure. @@ -909,17 +934,18 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onAjaxProgress(AjaxRequest ajaxRequest) { + FutureOr? onAjaxProgress(AjaxRequest ajaxRequest) { return null; } ///Event fired when a request is sent to a server through [Fetch API](https://developer.mozilla.org/it/docs/Web/API/Fetch_API). ///It gives the host application a chance to take control over the request before sending it. + ///This event is implemented using JavaScript under the hood. /// ///[fetchRequest] represents a resource request. /// ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewSettings.useShouldInterceptFetchRequest] setting to `true`. - ///Also, unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that + ///Also, on Android that doesn't support the [WebViewFeature.DOCUMENT_START_SCRIPT], unlike iOS that has [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript) that ///can inject javascript code right after the document element is created but before any other content is loaded, in Android the javascript code ///used to intercept fetch requests is loaded as soon as possible so it won't be instantaneous as iOS but just after some milliseconds (< ~100ms). ///Inside the `window.addEventListener("flutterInAppWebViewPlatformReady")` event, the fetch requests will be intercept for sure. @@ -928,7 +954,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? shouldInterceptFetchRequest( + FutureOr? shouldInterceptFetchRequest( FetchRequest fetchRequest) { return null; } @@ -965,7 +991,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ///- iOS ///- MacOS - Future? onPrintRequest( + FutureOr? onPrintRequest( WebUri? url, PlatformPrintJobController? printJobController) { return null; } @@ -1043,11 +1069,12 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onScaleChanged](https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float))) ///- iOS ([Official API - UIScrollViewDelegate.scrollViewDidZoom](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619409-scrollviewdidzoom)) + ///- Windows ([Official API - ICoreWebView2Controller.add_ZoomFactorChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller?view=webview2-1.0.2849.39#add_zoomfactorchanged)) void onZoomScaleChanged(double oldScale, double newScale) {} ///Use [onSafeBrowsingHit] instead. @Deprecated("Use onSafeBrowsingHit instead") - Future? androidOnSafeBrowsingHit( + FutureOr? androidOnSafeBrowsingHit( Uri url, SafeBrowsingThreat? threatType) { return null; } @@ -1063,14 +1090,14 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onSafeBrowsingHit](https://developer.android.com/reference/android/webkit/WebViewClient#onSafeBrowsingHit(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20int,%20android.webkit.SafeBrowsingResponse))) - Future? onSafeBrowsingHit( + FutureOr? onSafeBrowsingHit( WebUri url, SafeBrowsingThreat? threatType) { return null; } ///Use [onPermissionRequest] instead. @Deprecated("Use onPermissionRequest instead") - Future? androidOnPermissionRequest( + FutureOr? androidOnPermissionRequest( String origin, List resources) { return null; } @@ -1091,14 +1118,14 @@ abstract class PlatformInAppBrowserEvents { ///- iOS ///- MacOS ///- Windows ([Official API - ICoreWebView2.add_PermissionRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#add_permissionrequested)) - Future? onPermissionRequest( + FutureOr? onPermissionRequest( PermissionRequest permissionRequest) { return null; } ///Use [onGeolocationPermissionsShowPrompt] instead. @Deprecated("Use onGeolocationPermissionsShowPrompt instead") - Future? + FutureOr? androidOnGeolocationPermissionsShowPrompt(String origin) { return null; } @@ -1111,7 +1138,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onGeolocationPermissionsShowPrompt](https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback))) - Future? + FutureOr? onGeolocationPermissionsShowPrompt(String origin) { return null; } @@ -1129,7 +1156,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [shouldInterceptRequest] instead. @Deprecated("Use shouldInterceptRequest instead") - Future? androidShouldInterceptRequest( + FutureOr? androidShouldInterceptRequest( WebResourceRequest request) { return null; } @@ -1151,14 +1178,14 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.shouldInterceptRequest](https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest))) ///- Windows ([ICoreWebView2.add_WebResourceRequested](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2478.35#add_webresourcerequested)) - Future? shouldInterceptRequest( + FutureOr? shouldInterceptRequest( WebResourceRequest request) { return null; } ///Use [onRenderProcessUnresponsive] instead. @Deprecated("Use onRenderProcessUnresponsive instead") - Future? androidOnRenderProcessUnresponsive( + FutureOr? androidOnRenderProcessUnresponsive( Uri? url) { return null; } @@ -1182,14 +1209,15 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewRenderProcessClient.onRenderProcessUnresponsive](https://developer.android.com/reference/android/webkit/WebViewRenderProcessClient#onRenderProcessUnresponsive(android.webkit.WebView,%20android.webkit.WebViewRenderProcess))) - Future? onRenderProcessUnresponsive( + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) + FutureOr? onRenderProcessUnresponsive( WebUri? url) { return null; } ///Use [onRenderProcessResponsive] instead. @Deprecated("Use onRenderProcessResponsive instead") - Future? androidOnRenderProcessResponsive( + FutureOr? androidOnRenderProcessResponsive( Uri? url) { return null; } @@ -1206,7 +1234,8 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewRenderProcessClient.onRenderProcessResponsive](https://developer.android.com/reference/android/webkit/WebViewRenderProcessClient#onRenderProcessResponsive(android.webkit.WebView,%20android.webkit.WebViewRenderProcess))) - Future? onRenderProcessResponsive(WebUri? url) { + FutureOr? onRenderProcessResponsive( + WebUri? url) { return null; } @@ -1218,17 +1247,21 @@ abstract class PlatformInAppBrowserEvents { ///The application's implementation of this callback should only attempt to clean up the WebView. ///The WebView should be removed from the view hierarchy, all references to it should be cleaned up. /// + ///To cause an render process crash for test purpose, the application can call load url `"chrome://crash"` on the WebView. + ///Note that multiple WebView instances may be affected if they share a render process, not just the specific WebView which loaded `"chrome://crash"`. + /// ///[detail] the reason why it exited. /// ///**NOTE**: available only on Android 26+. /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onRenderProcessGone](https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail))) + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) void onRenderProcessGone(RenderProcessGoneDetail detail) {} ///Use [onFormResubmission] instead. @Deprecated('Use onFormResubmission instead') - Future? androidOnFormResubmission(Uri? url) { + FutureOr? androidOnFormResubmission(Uri? url) { return null; } @@ -1236,7 +1269,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebViewClient.onFormResubmission](https://developer.android.com/reference/android/webkit/WebViewClient#onFormResubmission(android.webkit.WebView,%20android.os.Message,%20android.os.Message))) - Future? onFormResubmission(WebUri? url) { + FutureOr? onFormResubmission(WebUri? url) { return null; } @@ -1272,7 +1305,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [onJsBeforeUnload] instead. @Deprecated('Use onJsBeforeUnload instead') - Future? androidOnJsBeforeUnload( + FutureOr? androidOnJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @@ -1289,7 +1322,7 @@ abstract class PlatformInAppBrowserEvents { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - WebChromeClient.onJsBeforeUnload](https://developer.android.com/reference/android/webkit/WebChromeClient#onJsBeforeUnload(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult))) - Future? onJsBeforeUnload( + FutureOr? onJsBeforeUnload( JsBeforeUnloadRequest jsBeforeUnloadRequest) { return null; } @@ -1330,10 +1363,12 @@ abstract class PlatformInAppBrowserEvents { void iosOnWebContentProcessDidTerminate() {} ///Invoked when the web view's web content process is terminated. + ///Reloading the page will start a new render process if needed. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webViewWebContentProcessDidTerminate](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi)) ///- MacOS ([Official API - WKNavigationDelegate.webViewWebContentProcessDidTerminate](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi)) + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) void onWebContentProcessDidTerminate() {} ///Use [onDidReceiveServerRedirectForProvisionalNavigation] instead. @@ -1349,7 +1384,7 @@ abstract class PlatformInAppBrowserEvents { ///Use [onNavigationResponse] instead. @Deprecated('Use onNavigationResponse instead') - Future? iosOnNavigationResponse( + FutureOr? iosOnNavigationResponse( IOSWKNavigationResponse navigationResponse) { return null; } @@ -1363,14 +1398,14 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview)) - Future? onNavigationResponse( + FutureOr? onNavigationResponse( NavigationResponse navigationResponse) { return null; } ///Use [shouldAllowDeprecatedTLS] instead. @Deprecated('Use shouldAllowDeprecatedTLS instead') - Future? iosShouldAllowDeprecatedTLS( + FutureOr? iosShouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @@ -1386,7 +1421,7 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/3601237-webview)) ///- MacOS ([Official API - WKNavigationDelegate.webView](https://developer.apple.com/documentation/webkit/wknavigationdelegate/3601237-webview)) - Future? shouldAllowDeprecatedTLS( + FutureOr? shouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) { return null; } @@ -1429,4 +1464,48 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- iOS void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} + + ///Invoked when any of the processes in the WebView Process Group encounters one of the following conditions: + ///- Unexpected exit: The process indicated by the event args has exited unexpectedly (usually due to a crash). + ///The failure might or might not be recoverable and some failures are auto-recoverable. + ///- Unresponsiveness: The process indicated by the event args has become unresponsive to user input. + ///This is only reported for renderer processes, and will run every few seconds until the process becomes responsive again. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2.add_ProcessFailed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2849.39#add_processfailed)) + void onProcessFailed(ProcessFailedDetail detail) {} + + ///This event runs when an accelerator key or key combo is pressed or + ///released while the WebView is focused. + ///A key is considered an accelerator if either of the following conditions are `true`: + ///- `Ctrl` or `Alt` is currently being held. + ///- The pressed key does not map to a character. + /// + ///A few specific keys are never considered accelerators, such as `Shift`. + ///The `Escape` key is always considered an accelerator. + /// + ///Auto-repeated key events caused by holding the key down also triggers this event. + ///Filter out the auto-repeated key events by verifying the [AcceleratorKeyPressedDetail.physicalKeyStatus] property. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2Controller.add_AcceleratorKeyPressed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller?view=webview2-1.0.2849.39#add_acceleratorkeypressed)) + void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + ///Tell the client to show a file chooser. + ///This is called to handle HTML forms with 'file' input type, + ///in response to the user pressing the "Select File" button. + ///To cancel the request, return a [ShowFileChooserResponse] with [ShowFileChooserResponse.filePaths] to `null`. + /// + ///Note that the WebView does not enforce any restrictions on the chosen file(s). + ///WebView can access all files that your app can access. + ///In case the file(s) are chosen through an untrusted source such as a third-party app, + ///it is your own app's responsibility to check what the returned Uris refer + ///to. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - WebChromeClient.onShowFileChooser](https://developer.android.com/reference/android/webkit/WebChromeClient#onShowFileChooser(android.webkit.WebView,%20android.webkit.ValueCallback%3Candroid.net.Uri[]%3E,%20android.webkit.WebChromeClient.FileChooserParams))) + FutureOr onShowFileChooser( + ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart index f1805c109..3cd048b90 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_localhost_server.dart @@ -38,6 +38,7 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { bool _shared = false; String _directoryIndex = 'index.html'; String _documentRoot = './'; + Future Function(HttpRequest)? _customOnData; /// Creates a new [DefaultInAppLocalhostServer]. DefaultInAppLocalhostServer(PlatformInAppLocalhostServerCreationParams params) @@ -53,6 +54,7 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { ? params.documentRoot : '${params.documentRoot}/'; this._shared = params.shared; + this._customOnData = params.onData; } @override @@ -67,6 +69,9 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { @override bool get shared => _shared; + @override + Future Function(HttpRequest request)? get onData => _customOnData; + @override Future start() async { if (this._started) { @@ -78,11 +83,19 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { runZonedGuarded(() { HttpServer.bind('127.0.0.1', _port, shared: _shared).then((server) { - print('Server running on http://localhost:' + _port.toString()); + if (kDebugMode) { + print('Server running on http://localhost:' + _port.toString()); + } this._server = server; server.listen((HttpRequest request) async { + if (await _customOnData?.call(request) ?? false) { + // if _customOnData returns true, + // it means that the request has been handled + return; + } + Uint8List body = Uint8List(0); var path = request.requestedUri.path; @@ -99,8 +112,10 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { .buffer .asUint8List(); } catch (e) { - print(Uri.decodeFull(path)); - print(e.toString()); + if (kDebugMode) { + print(Uri.decodeFull(path)); + print(e.toString()); + } request.response.close(); return; } @@ -115,13 +130,18 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { } request.response.headers.contentType = contentType; + print(request.response.headers); request.response.add(body); request.response.close(); }); completer.complete(); }); - }, (e, stackTrace) => print('Error: $e $stackTrace')); + }, (e, stackTrace) { + if (kDebugMode) { + print('Error: $e $stackTrace'); + } + }); return completer.future; } @@ -132,7 +152,9 @@ class DefaultInAppLocalhostServer extends PlatformInAppLocalhostServer { return; } await this._server!.close(force: true); - print('Server running on http://localhost:$_port closed'); + if (kDebugMode) { + print('Server running on http://localhost:$_port closed'); + } this._started = false; this._server = null; } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart index 0cf7800e8..cd7cff328 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_keep_alive.dart @@ -22,7 +22,7 @@ extension InternalInAppWebViewKeepAlive on InAppWebViewKeepAlive { ///Used internally to save and restore [PlatformInAppWebViewController] properties ///for the keep alive feature. class InAppWebViewControllerKeepAliveProps { - Map javaScriptHandlersMap; + Map javaScriptHandlersMap; Map> userScripts; Set webMessageListenerObjNames; Map injectedScriptsFromURL; diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart index 44ab686cd..2fa1c655b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart @@ -1,9 +1,10 @@ +import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; -import 'dart:typed_data'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../platform_webview_asset_loader.dart'; import '../types/action_mode_menu_item.dart'; import '../types/cache_mode.dart'; import '../types/data_detector_types.dart'; @@ -12,6 +13,7 @@ import '../types/force_dark_strategy.dart'; import '../types/layout_algorithm.dart'; import '../types/mixed_content_mode.dart'; import '../types/over_scroll_mode.dart'; +import '../types/pdf_toolbar_items.dart'; import '../types/referrer_policy.dart'; import '../types/renderer_priority_policy.dart'; import '../types/sandbox.dart'; @@ -21,29 +23,19 @@ import '../types/scrollview_deceleration_rate.dart'; import '../types/selection_granularity.dart'; import '../types/user_preferred_content_mode.dart'; import '../types/vertical_scrollbar_position.dart'; -import '../web_uri.dart'; -import 'android/in_app_webview_options.dart'; -import 'apple/in_app_webview_options.dart'; -import '../content_blocker.dart'; -import '../types/main.dart'; -import '../util.dart'; -import '../in_app_browser/in_app_browser_settings.dart'; -import '../platform_webview_feature.dart'; -import '../in_app_webview/platform_inappwebview_controller.dart'; -import '../context_menu/context_menu.dart'; -import '../in_app_browser/platform_in_app_browser.dart'; -import 'platform_webview.dart'; part 'in_app_webview_settings.g.dart'; List _deserializeContentBlockers( - List? contentBlockersMapList) { + List? contentBlockersMapList, + {EnumMethod? enumMethod}) { List contentBlockers = []; if (contentBlockersMapList != null) { contentBlockersMapList.forEach((contentBlocker) { contentBlockers.add(ContentBlocker.fromMap( Map>.from( - Map.from(contentBlocker)))); + Map.from(contentBlocker)), + enumMethod: enumMethod)); }); } return contentBlockers; @@ -85,6 +77,7 @@ class InAppWebViewSettings_ { ///Use [PlatformInAppWebViewController.clearAllCache] instead. @Deprecated("Use InAppWebViewController.clearAllCache instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms( platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? clearCache; @@ -271,15 +264,46 @@ class InAppWebViewSettings_ { ///Due to the async nature of [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event implementation, ///it will intercept only async `XMLHttpRequest`s ([AjaxRequest.isAsync] with `true`). ///To be able to intercept sync `XMLHttpRequest`s, use [InAppWebViewSettings.interceptOnlyAsyncAjaxRequests] to `false`. + ///If necessary, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. /// - ///If the [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event or - ///any other Ajax event is implemented and this value is `null`, + ///If the [PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event is implemented and this value is `null`, ///it will be automatically inferred as `true`, otherwise, the default value is `false`. ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. @SupportedPlatforms( platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) bool? useShouldInterceptAjaxRequest; + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onAjaxReadyStateChange] event. + ///Also, [useShouldInterceptAjaxRequest] must be set to `true` to take effect. + /// + ///Due to the async nature of [PlatformWebViewCreationParams.onAjaxReadyStateChange] event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. + /// + ///If the [PlatformWebViewCreationParams.onAjaxReadyStateChange] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + bool? useOnAjaxReadyStateChange; + + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onAjaxProgress] event. + ///Also, [useShouldInterceptAjaxRequest] must be set to `true` to take effect. + /// + ///Due to the async nature of [PlatformWebViewCreationParams.onAjaxProgress] event implementation, + ///using it could cause some issues, so, be careful when using it. + ///In this case, you should implement your own logic using for example an [UserScript] overriding the + ///[XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) JavaScript object. + /// + ///If the [PlatformWebViewCreationParams.onAjaxProgress] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform(), IOSPlatform(), MacOSPlatform()]) + bool? useOnAjaxProgress; + ///Set to `false` to be able to listen to also sync `XMLHttpRequest`s at the ///[PlatformWebViewCreationParams.shouldInterceptAjaxRequest] event. /// @@ -308,9 +332,9 @@ because there isn't any way to make the website data store non-persistent for th IOSPlatform(), MacOSPlatform(), WindowsPlatform( - apiName: "ICoreWebView2ControllerOptions.put_IsInPrivateModeEnabled", - apiUrl: "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controlleroptions?view=webview2-1.0.2792.45#put_isinprivatemodeenabled" - ) + apiName: "ICoreWebView2ControllerOptions.put_IsInPrivateModeEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controlleroptions?view=webview2-1.0.2792.45#put_isinprivatemodeenabled") ]) bool? incognito; @@ -433,6 +457,7 @@ because there isn't any way to make the website data store non-persistent for th ///Use [PlatformCookieManager.removeSessionCookies] instead. @Deprecated("Use CookieManager.removeSessionCookies instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [AndroidPlatform()]) bool? clearSessionCache; @@ -622,7 +647,17 @@ because there isn't any way to make the website data store non-persistent for th ]) String? fixedFontFamily; + ///Use [algorithmicDarkeningAllowed] instead. + /// ///Set the force dark mode for this WebView. The default value is [ForceDark.OFF]. + /// + ///Deprecated - The "force dark" model previously implemented by WebView was complex and didn't + ///interoperate well with current Web standards for `prefers-color-scheme` and `color-scheme`. + ///In apps with `targetSdkVersion` ≥ `android.os.Build.VERSION_CODES.TIRAMISU` this API is a no-op and + ///WebView will always use the dark style defined by web content authors if the app's theme is dark. + ///To customize the behavior, refer to [algorithmicDarkeningAllowed]. + @Deprecated("Use algorithmicDarkeningAllowed instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( available: "29", @@ -632,10 +667,18 @@ because there isn't any way to make the website data store non-persistent for th ]) ForceDark_? forceDark; - ///Sets whether Geolocation API is enabled. The default value is `true`. - + ///Use [algorithmicDarkeningAllowed] instead. + /// ///Set how WebView content should be darkened. ///The default value is [ForceDarkStrategy.PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING]. + /// + ///Deprecated - The "force dark" model previously implemented by WebView was complex and didn't + ///interoperate well with current Web standards for `prefers-color-scheme` and `color-scheme`. + ///In apps with `targetSdkVersion` ≥ `android.os.Build.VERSION_CODES.TIRAMISU` this API is a no-op and + ///WebView will always use the dark style defined by web content authors if the app's theme is dark. + ///To customize the behavior, refer to [algorithmicDarkeningAllowed]. + @Deprecated("Use algorithmicDarkeningAllowed instead") + @ExchangeableObjectProperty(leaveDeprecatedInToMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( apiName: "WebSettingsCompat.setForceDarkStrategy", @@ -764,6 +807,9 @@ because there isn't any way to make the website data store non-persistent for th ///Sets whether the WebView should save form data. In Android O, the platform has implemented a fully functional Autofill feature to store form data. ///Therefore, the Webview form data save feature is disabled. Note that the feature will continue to be supported on older versions of Android as before. ///The default value is `true`. + @Deprecated('') + @ExchangeableObjectProperty( + leaveDeprecatedInToMapMethod: true, leaveDeprecatedInFromMapMethod: true) @SupportedPlatforms(platforms: [ AndroidPlatform( apiName: "WebSettings.setSaveFormData", @@ -804,11 +850,19 @@ because there isn't any way to make the website data store non-persistent for th ]) bool? supportMultipleWindows; - ///Regular expression used by [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event to cancel navigation requests for frames that are not the main frame. - ///If the url request of a subframe matches the regular expression, then the request of that subframe is canceled. + ///Regular expression used on native side by the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] + ///event to cancel navigation requests for frames that are not the main frame. + ///If the url request of a sub-frame matches the regular expression, then the request of that sub-frame is canceled. @SupportedPlatforms(platforms: [AndroidPlatform()]) String? regexToCancelSubFramesLoading; + ///Regular expression used on native side by the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] + ///event to allow navigation requests synchronously. + ///If the url request match the regular expression, then the request is allowed automatically, + ///and the [PlatformWebViewCreationParams.shouldOverrideUrlLoading] event will not be fired. + @SupportedPlatforms(platforms: [AndroidPlatform()]) + String? regexToAllowSyncUrlLoading; + ///Set to `false` to disable Flutter Hybrid Composition. The default value is `true`. ///Hybrid Composition is supported starting with Flutter v1.20+. @SupportedPlatforms(platforms: [ @@ -919,7 +973,13 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ///Sets whether the default Android WebView’s internal error page should be suppressed or displayed for bad navigations. ///`true` means suppressed (not shown), `false` means it will be displayed. The default value is `false`. - @SupportedPlatforms(platforms: [AndroidPlatform()]) + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsBuiltInErrorPageEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2849.39#put_isbuiltinerrorpageenabled'), + ]) bool? disableDefaultErrorPage; ///Sets the vertical scrollbar thumb color. @@ -1057,7 +1117,18 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ]) bool? allowsAirPlayForMediaPlayback; - ///Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. + ///Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. + /// + ///**NOTE for Windows**: Swiping down to refresh is off by default and not exposed via API currently, + ///it requires the "--pull-to-refresh" option to be included in + ///the additional browser arguments to be configured. + ///(See [WebViewEnvironmentSettings.additionalBrowserArguments].). + ///When set to `false`, the end user cannot swipe to navigate or pull to refresh. + ///This API only affects the overscrolling navigation functionality and has + ///no effect on the scrolling interaction used to explore the web content shown in WebView2. + ///Disabling/Enabling [allowsBackForwardNavigationGestures] takes effect after the next navigation. + /// + ///The default value is `true`. @SupportedPlatforms(platforms: [ IOSPlatform( apiName: "WKWebView.allowsBackForwardNavigationGestures", @@ -1066,7 +1137,12 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri MacOSPlatform( apiName: "WKWebView.allowsBackForwardNavigationGestures", apiUrl: - "https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu") + "https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu"), + WindowsPlatform( + available: "1.0.992.28", + apiName: "ICoreWebView2Settings6.put_IsSwipeNavigationEnabled", + apiUrl: + "https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings6?view=webview2-1.0.2849.39#put_isswipenavigationenabled"), ]) bool? allowsBackForwardNavigationGestures; @@ -1594,6 +1670,353 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ]) bool? shouldPrintBackgrounds; + ///A [Set] of Regular Expression Patterns that will be used on native side to match the allowed origins + ///that are able to execute the JavaScript Handlers defined for the current WebView. + ///This will affect also the internal JavaScript Handlers used by the plugin itself. + /// + ///An empty [Set] will block every origin. + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? javaScriptHandlersOriginAllowList; + + ///Set to `true` to allow to execute the JavaScript Handlers only on the main frame. + ///This will affect also the internal JavaScript Handlers used by the plugin itself. + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptHandlersForMainFrameOnly; + + ///Set to `false` to disable the JavaScript Bridge completely. + ///This will affect also all the internal plugin [UserScript]s + ///that are using the JavaScript Bridge to work. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptBridgeEnabled; + + ///A [Set] of patterns that will be used to match the allowed origins where + ///the JavaScript Bridge could be used. + ///If [pluginScriptsOriginAllowList] is present, then this value will override + ///it only for the JavaScript Bridge internal plugin. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin and, in this case, + ///it will force the behaviour of the [javaScriptBridgeEnabled] parameter, + ///as it was set to `false`. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? javaScriptBridgeOriginAllowList; + + ///Set to `true` to allow the JavaScript Bridge only on the main frame. + ///If [pluginScriptsForMainFrameOnly] is present, then this value will override + ///it only for the JavaScript Bridge internal plugin. + /// + ///**NOTE**: setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? javaScriptBridgeForMainFrameOnly; + + ///A [Set] of patterns that will be used to match the allowed origins + ///that are able to load all the internal plugin [UserScript]s used by the plugin itself. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. + /// + ///**NOTE**: If [javaScriptBridgeOriginAllowList] is not present, this value will affect also the JavaScript Bridge internal plugin. + ///Also, setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///The default value is `null` and will allow every origin. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + Set? pluginScriptsOriginAllowList; + + ///Set to `true` to allow internal plugin [UserScript]s only on the main frame. + /// + ///**NOTE**: If [javaScriptBridgeForMainFrameOnly] is not present, this value will affect also the JavaScript Bridge internal plugin. + ///Also, setting or changing this value after the WebView has been created won't have any effect. + ///It should be set when initializing the WebView through [PlatformWebViewCreationParams.initialSettings] parameter. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) + bool? pluginScriptsForMainFrameOnly; + + ///The multiplier applied to the scroll amount for the WebView. + /// + ///This value determines how much the content will scroll in response to user input. + ///A higher value means faster scrolling, while a lower value means slower scrolling. + /// + ///The default value is `1`. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? scrollMultiplier; + + ///Specifies whether the status bar is displayed. + /// + ///The status bar is usually displayed in the lower left of the WebView and + ///shows things such as the URI of a link when the user hovers over it and other information. + ///The status bar UI can be altered by web content and should not be considered secure. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + apiName: "ICoreWebView2Settings.put_IsStatusBarEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings?view=webview2-1.0.2849.39#put_isstatusbarenabled'), + ]) + bool? statusBarEnabled; + + ///When this setting is set to `false`, it disables all accelerator keys + ///that access features specific to a web browser, including but not limited to: + ///- Ctrl-F and F3 for Find on Page + ///- Ctrl-P for Print + ///- Ctrl-R and F5 for Reload + ///- Ctrl-Plus and Ctrl-Minus for zooming + ///- Ctrl-Shift-C and F12 for DevTools + ///Special keys for browser functions, such as Back, Forward, and Search + ///It does not disable accelerator keys related to movement and text editing, such as: + ///- Home, End, Page Up, and Page Down + ///- Ctrl-X, Ctrl-C, Ctrl-V + ///- Ctrl-A for Select All + ///- Ctrl-Z for Undo + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.864.35', + apiName: "ICoreWebView2Settings3.put_IsBuiltInErrorPageEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings3?view=webview2-1.0.2849.39#put_arebrowseracceleratorkeysenabled'), + ]) + bool? browserAcceleratorKeysEnabled; + + ///Specifies whether autofill for information like names, street and email addresses, phone numbers, and arbitrary input is enabled. + /// + ///This excludes password and credit card information. + ///When [generalAutofillEnabled] is `false`, no suggestions appear, and no new information is saved. + ///When [generalAutofillEnabled] is `true`, information is saved, suggestions appear + ///and clicking on one will populate the form fields. + ///It will take effect immediately after setting. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings4.put_IsGeneralAutofillEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings4?view=webview2-1.0.2849.39#put_isgeneralautofillenabled'), + ]) + bool? generalAutofillEnabled; + + ///Specifies whether autosave for password information is enabled. + /// + ///The [passwordAutosaveEnabled] property behaves independently of the IsGeneralAutofillEnabled property. + ///When [passwordAutosaveEnabled] is `false`, no new password data is saved and no Save/Update Password prompts are displayed. + ///However, if there was password data already saved before disabling this setting, then that password + ///information is auto-populated, suggestions are shown and clicking on one will populate the fields. + ///When [passwordAutosaveEnabled] is `true`, password information is auto-populated, + ///suggestions are shown and clicking on one will populate the fields, + ///new data is saved, and a Save/Update Password prompt is displayed. + ///It will take effect immediately after setting. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings4.put_IsPasswordAutosaveEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings4?view=webview2-1.0.2849.39#put_ispasswordautosaveenabled'), + ]) + bool? passwordAutosaveEnabled; + + ///Pinch-zoom, referred to as "Page Scale" zoom, is performed as a post-rendering step, + ///it changes the page scale factor property and scales the surface the web page + ///is rendered onto when user performs a pinch zooming action. + /// + ///It does not change the layout but rather changes the viewport and clips the + ///web content, the content outside of the viewport isn't visible onscreen and users can't reach this content using mouse. + /// + ///The [pinchZoomEnabled] property enables or disables the ability of the end user + ///to use a pinching motion on touch input enabled devices to scale the web content in the WebView2. + ///When set to `false`, the end user cannot pinch zoom after the next navigation. + ///Disabling/Enabling [pinchZoomEnabled] only affects the end user's ability to + ///use pinch motions and does not change the page scale factor. + ///This API only affects the Page Scale zoom and has no effect on the existing + ///browser zoom properties or other end user mechanisms for zooming. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.902.49', + apiName: "ICoreWebView2Settings5.put_IsPinchZoomEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings5?view=webview2-1.0.2849.39#put_ispinchzoomenabled'), + ]) + bool? pinchZoomEnabled; + + ///This property is used to customize the PDF toolbar items. + /// + ///By default, it is [PdfToolbarItems.NONE] and so it displays all of the items. + ///Changes to this property apply to all CoreWebView2s in the same environment and using the same profile. + ///Changes to this setting apply only after the next navigation. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1185.39', + apiName: "ICoreWebView2Settings7.put_HiddenPdfToolbarItems", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings7?view=webview2-1.0.2849.39#put_hiddenpdftoolbaritems'), + ]) + PdfToolbarItems_? hiddenPdfToolbarItems; + + ///[reputationCheckingRequired] is used to control whether SmartScreen enabled or not. + /// + ///SmartScreen helps webviews identify reported phishing and malware websites and also helps users make informed decisions about downloads. + ///SmartScreen is enabled or disabled for all CoreWebView2s using the same user data folder. + ///If [reputationCheckingRequired] is true for any CoreWebView2 using the same user data folder, then SmartScreen is enabled. + ///If [reputationCheckingRequired] is false for all CoreWebView2 using the same user data folder, then SmartScreen is disabled. + ///When it is changed, the change will be applied to all WebViews using the same user data folder on the next navigation or download. + ///If the newly created CoreWebview2 does not set SmartScreen to `false`, + ///when navigating(Such as Navigate(), LoadDataUrl(), ExecuteScript(), etc.), the default value will be applied to all CoreWebview2 using the same user data folder. + ///SmartScreen of WebView2 apps can be controlled by Windows system setting "SmartScreen for Microsoft Edge", specially, + ///for WebView2 in Windows Store apps, SmartScreen is controlled by another Windows system setting "SmartScreen for Microsoft Store apps". + ///When the Windows setting is enabled, the SmartScreen operates under the control of the [reputationCheckingRequired]. + ///When the Windows setting is disabled, the SmartScreen will be disabled regardless of the [reputationCheckingRequired] value set in WebView2 apps. + ///In other words, under this circumstance the value of [reputationCheckingRequired] will be saved but overridden by system setting. + ///Upon re-enabling the Windows setting, the CoreWebview2 will reference the [reputationCheckingRequired] to determine the SmartScreen status. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1722.45', + apiName: "ICoreWebView2Settings8.put_IsReputationCheckingRequired", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings8?view=webview2-1.0.2849.39#put_isreputationcheckingrequired'), + ]) + bool? reputationCheckingRequired; + + ///Enables web pages to use the `app-region` CSS style. + /// + ///Disabling/Enabling the [nonClientRegionSupportEnabled] takes effect after the next navigation. + /// + ///When this property is `true`, then all the non-client region features will be enabled: + ///Draggable Regions will be enabled, they are regions on a webpage that are marked with the CSS attribute `app-region: drag/no-drag`. + ///When set to drag, these regions will be treated like the window's title bar, + ///supporting dragging of the entire WebView and its host app window; + ///the system menu shows upon right click, and a double click will trigger maximizing/restoration of the window size. + /// + ///When set to `false`, all non-client region support will be disabled. + ///The `app-region` CSS style will be ignored on web pages. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2420.47', + apiName: "ICoreWebView2Settings9.put_IsNonClientRegionSupportEnabled", + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings9?view=webview2-1.0.2849.39#put_isnonclientregionsupportenabled'), + ]) + bool? nonClientRegionSupportEnabled; + + ///A Boolean value that determines whether user events are ignored and removed from the event queue. + /// + ///The default value is `true`. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform( + apiName: "UIView.isUserInteractionEnabled", + apiUrl: + 'https://developer.apple.com/documentation/uikit/uiview/1622577-isuserinteractionenabled'), + ]) + bool? isUserInteractionEnabled; + + ///A Boolean value that determines whether to listen and handle the + ///[PlatformWebViewCreationParams.onAcceleratorKeyPressed] event. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + bool? handleAcceleratorKeyPressed; + + ///The view’s alpha value. The value of this property is a floating-point number + ///in the range 0.0 to 1.0, where 0.0 represents totally transparent and 1.0 represents totally opaque. + @SupportedPlatforms(platforms: [ + AndroidPlatform( + apiName: "View.setAlpha", + apiUrl: + 'https://developer.android.com/reference/android/view/View#setAlpha(float)'), + IOSPlatform( + apiName: "UIView.alpha", + apiUrl: + 'https://developer.apple.com/documentation/uikit/uiview/1622417-alpha'), + MacOSPlatform( + apiName: "NSView.alphaValue", + apiUrl: + 'https://developer.apple.com/documentation/appkit/nsview/1483560-alphavalue'), + ]) + double? alpha; + + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onShowFileChooser] event. + /// + ///If the [PlatformWebViewCreationParams.onShowFileChooser] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform()]) + bool? useOnShowFileChooser; + ///Specifies a feature policy for the `. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? name; + + ///The unique identifier of the frame associated with the current [FrameInfo]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? frameId; + + ///The kind of the frame. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + FrameKind_? kind; + FrameInfo_( - {required this.isMainFrame, required this.request, this.securityOrigin}); + {required this.isMainFrame, + required this.request, + this.securityOrigin, + this.name, + this.frameId, + this.kind}); } ///An object that contains information about a frame on a webpage. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart index 71f0402a5..967e94d44 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_info.g.dart @@ -8,36 +8,92 @@ part of 'frame_info.dart'; ///An object that contains information about a frame on a webpage. class FrameInfo { + ///The unique identifier of the frame associated with the current [FrameInfo]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? frameId; + ///A Boolean value indicating whether the frame is the web site's main frame or a subframe. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows bool isMainFrame; + ///The kind of the frame. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + FrameKind? kind; + + ///Gets the name attribute of the frame, as in . + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? name; + ///The frame’s current request. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows URLRequest? request; ///The frame’s security origin. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + ///- Windows SecurityOrigin? securityOrigin; - FrameInfo({required this.isMainFrame, this.request, this.securityOrigin}); + FrameInfo( + {this.frameId, + required this.isMainFrame, + this.kind, + this.name, + this.request, + this.securityOrigin}); ///Gets a possible [FrameInfo] instance from a [Map] value. - static FrameInfo? fromMap(Map? map) { + static FrameInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = FrameInfo( + frameId: map['frameId'], isMainFrame: map['isMainFrame'], - request: URLRequest.fromMap(map['request']?.cast()), + kind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => FrameKind.fromNativeValue(map['kind']), + EnumMethod.value => FrameKind.fromValue(map['kind']), + EnumMethod.name => FrameKind.byName(map['kind']) + }, + name: map['name'], + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod), securityOrigin: SecurityOrigin.fromMap( - map['securityOrigin']?.cast()), + map['securityOrigin']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { + "frameId": frameId, "isMainFrame": isMainFrame, - "request": request?.toMap(), - "securityOrigin": securityOrigin?.toMap(), + "kind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => kind?.toNativeValue(), + EnumMethod.value => kind?.toValue(), + EnumMethod.name => kind?.name() + }, + "name": name, + "request": request?.toMap(enumMethod: enumMethod), + "securityOrigin": securityOrigin?.toMap(enumMethod: enumMethod), }; } @@ -48,7 +104,7 @@ class FrameInfo { @override String toString() { - return 'FrameInfo{isMainFrame: $isMainFrame, request: $request, securityOrigin: $securityOrigin}'; + return 'FrameInfo{frameId: $frameId, isMainFrame: $isMainFrame, kind: $kind, name: $name, request: $request, securityOrigin: $securityOrigin}'; } } @@ -71,25 +127,28 @@ class IOSWKFrameInfo { {required this.isMainFrame, this.request, this.securityOrigin}); ///Gets a possible [IOSWKFrameInfo] instance from a [Map] value. - static IOSWKFrameInfo? fromMap(Map? map) { + static IOSWKFrameInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKFrameInfo( isMainFrame: map['isMainFrame'], - request: URLRequest.fromMap(map['request']?.cast()), + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod), securityOrigin: IOSWKSecurityOrigin.fromMap( - map['securityOrigin']?.cast()), + map['securityOrigin']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, - "request": request?.toMap(), - "securityOrigin": securityOrigin?.toMap(), + "request": request?.toMap(enumMethod: enumMethod), + "securityOrigin": securityOrigin?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart new file mode 100644 index 000000000..c3a6c6965 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.dart @@ -0,0 +1,64 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'frame_kind.g.dart'; + +///Class used to indicate the the frame kind. +@ExchangeableEnum() +class FrameKind_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const FrameKind_._internal(this._value); + + ///Indicates that the frame is an unknown type frame. We may extend this enum type to identify more frame kinds in the future. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_UNKNOWN', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 0), + ]) + static const UNKNOWN = const FrameKind_._internal('UNKNOWN'); + + ///Indicates that the frame is a primary main frame(webview). + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_MAIN_FRAME', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 1), + ]) + static const MAIN_FRAME = const FrameKind_._internal('MAIN_FRAME'); + + ///Indicates that the frame is an iframe. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_IFRAME', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 2), + ]) + static const IFRAME = const FrameKind_._internal('IFRAME'); + + ///Indicates that the frame is an embed element. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_EMBED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 3), + ]) + static const EMBED = const FrameKind_._internal('EMBED'); + + ///Indicates that the frame is an object element. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_FRAME_KIND_OBJECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind', + value: 4), + ]) + static const OBJECT = const FrameKind_._internal('OBJECT'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart new file mode 100644 index 000000000..a4162c3e0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/frame_kind.g.dart @@ -0,0 +1,185 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'frame_kind.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the the frame kind. +class FrameKind { + final String _value; + final dynamic _nativeValue; + const FrameKind._internal(this._value, this._nativeValue); +// ignore: unused_element + factory FrameKind._internalMultiPlatform( + String value, Function nativeValue) => + FrameKind._internal(value, nativeValue()); + + ///Indicates that the frame is an embed element. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_EMBED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final EMBED = FrameKind._internalMultiPlatform('EMBED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an iframe. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_IFRAME](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final IFRAME = FrameKind._internalMultiPlatform('IFRAME', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///Indicates that the frame is a primary main frame(webview). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_MAIN_FRAME](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final MAIN_FRAME = FrameKind._internalMultiPlatform('MAIN_FRAME', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an object element. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_OBJECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final OBJECT = FrameKind._internalMultiPlatform('OBJECT', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///Indicates that the frame is an unknown type frame. We may extend this enum type to identify more frame kinds in the future. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_FRAME_KIND_UNKNOWN](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_frame_kind)) + static final UNKNOWN = FrameKind._internalMultiPlatform('UNKNOWN', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///Set of all values of [FrameKind]. + static final Set values = [ + FrameKind.EMBED, + FrameKind.IFRAME, + FrameKind.MAIN_FRAME, + FrameKind.OBJECT, + FrameKind.UNKNOWN, + ].toSet(); + + ///Gets a possible [FrameKind] instance from [String] value. + static FrameKind? fromValue(String? value) { + if (value != null) { + try { + return FrameKind.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [FrameKind] instance from a native value. + static FrameKind? fromNativeValue(dynamic value) { + if (value != null) { + try { + return FrameKind.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [FrameKind] instance value with name [name]. + /// + /// Goes through [FrameKind.values] looking for a value with + /// name [name], as reported by [FrameKind.name]. + /// Returns the first value with the given name, otherwise `null`. + static FrameKind? byName(String? name) { + if (name != null) { + try { + return FrameKind.values.firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [FrameKind] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in FrameKind.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'EMBED': + return 'EMBED'; + case 'IFRAME': + return 'IFRAME'; + case 'MAIN_FRAME': + return 'MAIN_FRAME'; + case 'OBJECT': + return 'OBJECT'; + case 'UNKNOWN': + return 'UNKNOWN'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart index fa4cd16d6..b7ff1e847 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.dart @@ -1,5 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + part 'geolocation_permission_show_prompt_response.g.dart'; ///Class used by the host application to set the Geolocation permission state for an origin during the [PlatformWebViewCreationParams.onGeolocationPermissionsShowPrompt] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart index 37be906a5..d2d98a9cf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/geolocation_permission_show_prompt_response.g.dart @@ -22,7 +22,8 @@ class GeolocationPermissionShowPromptResponse { ///Gets a possible [GeolocationPermissionShowPromptResponse] instance from a [Map] value. static GeolocationPermissionShowPromptResponse? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -30,12 +31,14 @@ class GeolocationPermissionShowPromptResponse { allow: map['allow'], origin: map['origin'], ); - instance.retain = map['retain']; + if (map['retain'] != null) { + instance.retain = map['retain']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allow": allow, "origin": origin, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart index ce0340eb9..1f06fd0ce 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'http_auth_response_action.dart'; +import 'enum_method.dart'; part 'http_auth_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart index dfa18193a..0cf972f32 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response.g.dart @@ -26,22 +26,38 @@ class HttpAuthResponse { this.username = ""}); ///Gets a possible [HttpAuthResponse] instance from a [Map] value. - static HttpAuthResponse? fromMap(Map? map) { + static HttpAuthResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = HttpAuthResponse(); - instance.action = HttpAuthResponseAction.fromNativeValue(map['action']); - instance.password = map['password']; - instance.permanentPersistence = map['permanentPersistence']; - instance.username = map['username']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + HttpAuthResponseAction.fromNativeValue(map['action']), + EnumMethod.value => HttpAuthResponseAction.fromValue(map['action']), + EnumMethod.name => HttpAuthResponseAction.byName(map['action']) + }; + if (map['password'] != null) { + instance.password = map['password']; + } + if (map['permanentPersistence'] != null) { + instance.permanentPersistence = map['permanentPersistence']; + } + if (map['username'] != null) { + instance.username = map['username']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "password": password, "permanentPersistence": permanentPersistence, "username": username, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart index dbd5c5871..1199a0dca 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.dart @@ -12,12 +12,29 @@ class HttpAuthResponseAction_ { const HttpAuthResponseAction_._internal(this._value); ///Instructs the WebView to cancel the authentication request. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const CANCEL = const HttpAuthResponseAction_._internal(0); ///Instructs the WebView to proceed with the authentication with the given credentials. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + EnumWindowsPlatform(), + ]) static const PROCEED = const HttpAuthResponseAction_._internal(1); ///Uses the credentials stored for the current host. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + EnumIOSPlatform(), + EnumMacOSPlatform(), + ]) static const USE_SAVED_HTTP_AUTH_CREDENTIALS = const HttpAuthResponseAction_._internal(2); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart index 320124311..664ce22d8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_auth_response_action.g.dart @@ -17,12 +17,29 @@ class HttpAuthResponseAction { HttpAuthResponseAction._internal(value, nativeValue()); ///Instructs the WebView to cancel the authentication request. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const CANCEL = HttpAuthResponseAction._internal(0, 0); ///Instructs the WebView to proceed with the authentication with the given credentials. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows static const PROCEED = HttpAuthResponseAction._internal(1, 1); ///Uses the credentials stored for the current host. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS static const USE_SAVED_HTTP_AUTH_CREDENTIALS = HttpAuthResponseAction._internal(2, 2); @@ -59,20 +76,44 @@ class HttpAuthResponseAction { return null; } + /// Gets a possible [HttpAuthResponseAction] instance value with name [name]. + /// + /// Goes through [HttpAuthResponseAction.values] looking for a value with + /// name [name], as reported by [HttpAuthResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static HttpAuthResponseAction? byName(String? name) { + if (name != null) { + try { + return HttpAuthResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [HttpAuthResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in HttpAuthResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'CANCEL'; @@ -83,4 +124,15 @@ class HttpAuthResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart index 227ae10da..eb7dcc944 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.dart @@ -5,6 +5,7 @@ import 'url_response.dart'; import 'url_authentication_challenge.dart'; import 'url_protection_space.dart'; import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; part 'http_authentication_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart index 3481661d9..947d84e85 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_authentication_challenge.g.dart @@ -54,34 +54,39 @@ class HttpAuthenticationChallenge extends URLAuthenticationChallenge { } ///Gets a possible [HttpAuthenticationChallenge] instance from a [Map] value. - static HttpAuthenticationChallenge? fromMap(Map? map) { + static HttpAuthenticationChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = HttpAuthenticationChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, error: map['error'], - failureResponse: - URLResponse.fromMap(map['failureResponse']?.cast()), + failureResponse: URLResponse.fromMap( + map['failureResponse']?.cast(), + enumMethod: enumMethod), iosError: map['error'], iosFailureResponse: IOSURLResponse.fromMap( - map['failureResponse']?.cast()), + map['failureResponse']?.cast(), + enumMethod: enumMethod), previousFailureCount: map['previousFailureCount'], proposedCredential: URLCredential.fromMap( - map['proposedCredential']?.cast()), + map['proposedCredential']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), "error": error, - "failureResponse": failureResponse?.toMap(), + "failureResponse": failureResponse?.toMap(enumMethod: enumMethod), "previousFailureCount": previousFailureCount, - "proposedCredential": proposedCredential?.toMap(), + "proposedCredential": proposedCredential?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart index c21d5381b..7da454fc6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/http_cookie_same_site_policy.g.dart @@ -66,12 +66,55 @@ class HTTPCookieSameSitePolicy { return null; } + /// Gets a possible [HTTPCookieSameSitePolicy] instance value with name [name]. + /// + /// Goes through [HTTPCookieSameSitePolicy.values] looking for a value with + /// name [name], as reported by [HTTPCookieSameSitePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static HTTPCookieSameSitePolicy? byName(String? name) { + if (name != null) { + try { + return HTTPCookieSameSitePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [HTTPCookieSameSitePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in HTTPCookieSameSitePolicy.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'Lax': + return 'LAX'; + case 'None': + return 'NONE'; + case 'Strict': + return 'STRICT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart index cd5836305..7299bec02 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'in_app_webview_hit_test_result_type.dart'; +import 'enum_method.dart'; part 'in_app_webview_hit_test_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart index c022ecffe..5e719439f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result.g.dart @@ -16,22 +16,33 @@ class InAppWebViewHitTestResult { InAppWebViewHitTestResult({this.extra, this.type}); ///Gets a possible [InAppWebViewHitTestResult] instance from a [Map] value. - static InAppWebViewHitTestResult? fromMap(Map? map) { + static InAppWebViewHitTestResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = InAppWebViewHitTestResult( extra: map['extra'], - type: InAppWebViewHitTestResultType.fromNativeValue(map['type']), + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + InAppWebViewHitTestResultType.fromNativeValue(map['type']), + EnumMethod.value => + InAppWebViewHitTestResultType.fromValue(map['type']), + EnumMethod.name => InAppWebViewHitTestResultType.byName(map['type']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "extra": extra, - "type": type?.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type?.toNativeValue(), + EnumMethod.value => type?.toValue(), + EnumMethod.name => type?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart index 477e2b9bf..c395c99f3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_hit_test_result_type.g.dart @@ -79,20 +79,45 @@ class InAppWebViewHitTestResultType { return null; } + /// Gets a possible [InAppWebViewHitTestResultType] instance value with name [name]. + /// + /// Goes through [InAppWebViewHitTestResultType.values] looking for a value with + /// name [name], as reported by [InAppWebViewHitTestResultType.name]. + /// Returns the first value with the given name, otherwise `null`. + static InAppWebViewHitTestResultType? byName(String? name) { + if (name != null) { + try { + return InAppWebViewHitTestResultType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [InAppWebViewHitTestResultType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in InAppWebViewHitTestResultType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'EDIT_TEXT_TYPE'; @@ -113,4 +138,15 @@ class InAppWebViewHitTestResultType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart index 7a26edb3c..dae877f25 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'in_app_webview_initial_data.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart index 4cfd6cc9f..bfe52cee7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_initial_data.g.dart @@ -41,7 +41,8 @@ class InAppWebViewInitialData { } ///Gets a possible [InAppWebViewInitialData] instance from a [Map] value. - static InAppWebViewInitialData? fromMap(Map? map) { + static InAppWebViewInitialData? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -52,13 +53,17 @@ class InAppWebViewInitialData { data: map['data'], historyUrl: map['historyUrl'] != null ? WebUri(map['historyUrl']) : null, ); - instance.encoding = map['encoding']; - instance.mimeType = map['mimeType']; + if (map['encoding'] != null) { + instance.encoding = map['encoding']; + } + if (map['mimeType'] != null) { + instance.mimeType = map['mimeType']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "baseUrl": baseUrl?.toString(), "data": data, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart index e0c4775b7..f3f3992ad 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'in_app_webview_rect.g.dart'; ///A class that represents a structure that contains the location and dimensions of a rectangle. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart index fb059d53a..218566fa8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/in_app_webview_rect.g.dart @@ -28,7 +28,8 @@ class InAppWebViewRect { } ///Gets a possible [InAppWebViewRect] instance from a [Map] value. - static InAppWebViewRect? fromMap(Map? map) { + static InAppWebViewRect? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -42,7 +43,7 @@ class InAppWebViewRect { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "height": height, "width": width, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart index c0a7a7600..8f964e3af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.dart @@ -1,15 +1,37 @@ import 'dart:convert'; -import '../in_app_webview/platform_inappwebview_controller.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + +part 'javascript_handler_callback.g.dart'; + +///Use [JavaScriptHandlerFunction] instead. +@Deprecated('Use JavaScriptHandlerFunction instead') +typedef dynamic JavaScriptHandlerCallback(List arguments); ///This type represents a callback, added with [PlatformInAppWebViewController.addJavaScriptHandler], that listens to post messages sent from JavaScript. /// ///The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)). -///The iOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc) +///The iOS/macOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc) /// ///The JavaScript function that can be used to call the handler is `window.flutter_inappwebview.callHandler(handlerName , ...args);`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters). ///The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side. /// -///Also, a [JavaScriptHandlerCallback] can return json data to the JavaScript side. +///Also, a [JavaScriptHandlerFunction] can return json data to the JavaScript side. ///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library. -typedef dynamic JavaScriptHandlerCallback(List arguments); +typedef dynamic JavaScriptHandlerFunction(JavaScriptHandlerFunctionData data); + +///A class that represents the data passed to a [JavaScriptHandlerFunction] added with [PlatformInAppWebViewController.addJavaScriptHandler]. +@ExchangeableObject() +class JavaScriptHandlerFunctionData_ { + List args; + WebUri origin; + bool isMainFrame; + WebUri requestUrl; + + JavaScriptHandlerFunctionData_( + {this.args = const [], + required this.origin, + required this.isMainFrame, + required this.requestUrl}); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart new file mode 100644 index 000000000..253917169 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/javascript_handler_callback.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'javascript_handler_callback.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///A class that represents the data passed to a [JavaScriptHandlerFunction] added with [PlatformInAppWebViewController.addJavaScriptHandler]. +class JavaScriptHandlerFunctionData { + List args; + bool isMainFrame; + WebUri origin; + WebUri requestUrl; + JavaScriptHandlerFunctionData( + {this.args = const [], + required this.isMainFrame, + required this.origin, + required this.requestUrl}); + + ///Gets a possible [JavaScriptHandlerFunctionData] instance from a [Map] value. + static JavaScriptHandlerFunctionData? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = JavaScriptHandlerFunctionData( + isMainFrame: map['isMainFrame'], + origin: WebUri(map['origin']), + requestUrl: WebUri(map['requestUrl']), + ); + if (map['args'] != null) { + instance.args = List.from(map['args']!.cast()); + } + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "args": args, + "isMainFrame": isMainFrame, + "origin": origin.toString(), + "requestUrl": requestUrl.toString(), + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'JavaScriptHandlerFunctionData{args: $args, isMainFrame: $isMainFrame, origin: $origin, requestUrl: $requestUrl}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart index 401e7f78b..ad9f66fe9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_alert_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart index 9da0d33b3..1e8951577 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_request.g.dart @@ -33,7 +33,8 @@ class JsAlertRequest { } ///Gets a possible [JsAlertRequest] instance from a [Map] value. - static JsAlertRequest? fromMap(Map? map) { + static JsAlertRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -47,7 +48,7 @@ class JsAlertRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart index 1dbc54232..4666c9b07 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_alert_response_action.dart'; +import 'enum_method.dart'; part 'js_alert_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart index d6903df7c..710c0a0e0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response.g.dart @@ -26,22 +26,38 @@ class JsAlertResponse { this.message = ""}); ///Gets a possible [JsAlertResponse] instance from a [Map] value. - static JsAlertResponse? fromMap(Map? map) { + static JsAlertResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsAlertResponse(); - instance.action = JsAlertResponseAction.fromNativeValue(map['action']); - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsAlertResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsAlertResponseAction.fromValue(map['action']), + EnumMethod.name => JsAlertResponseAction.byName(map['action']) + }; + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart index 496541321..cef0b42bc 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_alert_response_action.g.dart @@ -50,12 +50,51 @@ class JsAlertResponseAction { return null; } + /// Gets a possible [JsAlertResponseAction] instance value with name [name]. + /// + /// Goes through [JsAlertResponseAction.values] looking for a value with + /// name [name], as reported by [JsAlertResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsAlertResponseAction? byName(String? name) { + if (name != null) { + try { + return JsAlertResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsAlertResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsAlertResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -64,10 +103,6 @@ class JsAlertResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart index df75b36f9..cba0144af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_before_unload_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart index 0cf1ed4f7..62d70b667 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_request.g.dart @@ -16,7 +16,8 @@ class JsBeforeUnloadRequest { JsBeforeUnloadRequest({this.message, this.url}); ///Gets a possible [JsBeforeUnloadRequest] instance from a [Map] value. - static JsBeforeUnloadRequest? fromMap(Map? map) { + static JsBeforeUnloadRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class JsBeforeUnloadRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "message": message, "url": url?.toString(), diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart index 0170db589..e2d5bb598 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_before_unload_response_action.dart'; +import 'enum_method.dart'; part 'js_before_unload_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart index 885f2f573..40ae46c68 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response.g.dart @@ -30,24 +30,41 @@ class JsBeforeUnloadResponse { this.message = ""}); ///Gets a possible [JsBeforeUnloadResponse] instance from a [Map] value. - static JsBeforeUnloadResponse? fromMap(Map? map) { + static JsBeforeUnloadResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsBeforeUnloadResponse(); - instance.action = - JsBeforeUnloadResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsBeforeUnloadResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsBeforeUnloadResponseAction.fromValue(map['action']), + EnumMethod.name => JsBeforeUnloadResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart index e82005ccb..eda8d59ce 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_before_unload_response_action.g.dart @@ -54,12 +54,54 @@ class JsBeforeUnloadResponseAction { return null; } + /// Gets a possible [JsBeforeUnloadResponseAction] instance value with name [name]. + /// + /// Goes through [JsBeforeUnloadResponseAction.values] looking for a value with + /// name [name], as reported by [JsBeforeUnloadResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsBeforeUnloadResponseAction? byName(String? name) { + if (name != null) { + try { + return JsBeforeUnloadResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsBeforeUnloadResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsBeforeUnloadResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +110,6 @@ class JsBeforeUnloadResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart index efd873029..0e2058389 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_confirm_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart index 3ab660da2..6d3497c3a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_request.g.dart @@ -33,7 +33,8 @@ class JsConfirmRequest { } ///Gets a possible [JsConfirmRequest] instance from a [Map] value. - static JsConfirmRequest? fromMap(Map? map) { + static JsConfirmRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -47,7 +48,7 @@ class JsConfirmRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isMainFrame": isMainFrame, "message": message, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart index a00083097..3ce0923df 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_confirm_response_action.dart'; +import 'enum_method.dart'; part 'js_confirm_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart index ddd0bc9be..f2eb05587 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response.g.dart @@ -30,23 +30,41 @@ class JsConfirmResponse { this.message = ""}); ///Gets a possible [JsConfirmResponse] instance from a [Map] value. - static JsConfirmResponse? fromMap(Map? map) { + static JsConfirmResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsConfirmResponse(); - instance.action = JsConfirmResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsConfirmResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsConfirmResponseAction.fromValue(map['action']), + EnumMethod.name => JsConfirmResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "handledByClient": handledByClient, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart index a9c706061..fff5abf6b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_confirm_response_action.g.dart @@ -54,12 +54,53 @@ class JsConfirmResponseAction { return null; } + /// Gets a possible [JsConfirmResponseAction] instance value with name [name]. + /// + /// Goes through [JsConfirmResponseAction.values] looking for a value with + /// name [name], as reported by [JsConfirmResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsConfirmResponseAction? byName(String? name) { + if (name != null) { + try { + return JsConfirmResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsConfirmResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsConfirmResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +109,6 @@ class JsConfirmResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart index 87dace2c9..d0e497dbf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'js_prompt_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart index 422d6d179..7ec6fad89 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_request.g.dart @@ -37,7 +37,8 @@ class JsPromptRequest { } ///Gets a possible [JsPromptRequest] instance from a [Map] value. - static JsPromptRequest? fromMap(Map? map) { + static JsPromptRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -52,7 +53,7 @@ class JsPromptRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "defaultValue": defaultValue, "isMainFrame": isMainFrame, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart index e262690b7..c3fa4cc32 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'js_prompt_response_action.dart'; +import 'enum_method.dart'; part 'js_prompt_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart index abea1745f..de84d987d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response.g.dart @@ -38,26 +38,46 @@ class JsPromptResponse { this.value}); ///Gets a possible [JsPromptResponse] instance from a [Map] value. - static JsPromptResponse? fromMap(Map? map) { + static JsPromptResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = JsPromptResponse( value: map['value'], ); - instance.action = JsPromptResponseAction.fromNativeValue(map['action']); - instance.cancelButtonTitle = map['cancelButtonTitle']; - instance.confirmButtonTitle = map['confirmButtonTitle']; - instance.defaultValue = map['defaultValue']; - instance.handledByClient = map['handledByClient']; - instance.message = map['message']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + JsPromptResponseAction.fromNativeValue(map['action']), + EnumMethod.value => JsPromptResponseAction.fromValue(map['action']), + EnumMethod.name => JsPromptResponseAction.byName(map['action']) + }; + if (map['cancelButtonTitle'] != null) { + instance.cancelButtonTitle = map['cancelButtonTitle']; + } + if (map['confirmButtonTitle'] != null) { + instance.confirmButtonTitle = map['confirmButtonTitle']; + } + if (map['defaultValue'] != null) { + instance.defaultValue = map['defaultValue']; + } + if (map['handledByClient'] != null) { + instance.handledByClient = map['handledByClient']; + } + if (map['message'] != null) { + instance.message = map['message']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "cancelButtonTitle": cancelButtonTitle, "confirmButtonTitle": confirmButtonTitle, "defaultValue": defaultValue, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart index ddebfa6a4..ff4257fb4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/js_prompt_response_action.g.dart @@ -54,12 +54,53 @@ class JsPromptResponseAction { return null; } + /// Gets a possible [JsPromptResponseAction] instance value with name [name]. + /// + /// Goes through [JsPromptResponseAction.values] looking for a value with + /// name [name], as reported by [JsPromptResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static JsPromptResponseAction? byName(String? name) { + if (name != null) { + try { + return JsPromptResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [JsPromptResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in JsPromptResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CANCEL'; + case 0: + return 'CONFIRM'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +109,6 @@ class JsPromptResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'CANCEL'; - case 0: - return 'CONFIRM'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart index def5e52ad..0f47008ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/layout_algorithm.g.dart @@ -63,12 +63,54 @@ class LayoutAlgorithm { return null; } + /// Gets a possible [LayoutAlgorithm] instance value with name [name]. + /// + /// Goes through [LayoutAlgorithm.values] looking for a value with + /// name [name], as reported by [LayoutAlgorithm.name]. + /// Returns the first value with the given name, otherwise `null`. + static LayoutAlgorithm? byName(String? name) { + if (name != null) { + try { + return LayoutAlgorithm.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [LayoutAlgorithm] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in LayoutAlgorithm.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NARROW_COLUMNS': + return 'NARROW_COLUMNS'; + case 'NORMAL': + return 'NORMAL'; + case 'TEXT_AUTOSIZING': + return 'TEXT_AUTOSIZING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -140,12 +182,55 @@ class AndroidLayoutAlgorithm { return null; } + /// Gets a possible [AndroidLayoutAlgorithm] instance value with name [name]. + /// + /// Goes through [AndroidLayoutAlgorithm.values] looking for a value with + /// name [name], as reported by [AndroidLayoutAlgorithm.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidLayoutAlgorithm? byName(String? name) { + if (name != null) { + try { + return AndroidLayoutAlgorithm.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidLayoutAlgorithm] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidLayoutAlgorithm.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NARROW_COLUMNS': + return 'NARROW_COLUMNS'; + case 'NORMAL': + return 'NORMAL'; + case 'TEXT_AUTOSIZING': + return 'TEXT_AUTOSIZING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart index 67db5301e..db21271b9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/layout_in_display_cutout_mode.g.dart @@ -70,20 +70,45 @@ class LayoutInDisplayCutoutMode { return null; } + /// Gets a possible [LayoutInDisplayCutoutMode] instance value with name [name]. + /// + /// Goes through [LayoutInDisplayCutoutMode.values] looking for a value with + /// name [name], as reported by [LayoutInDisplayCutoutMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static LayoutInDisplayCutoutMode? byName(String? name) { + if (name != null) { + try { + return LayoutInDisplayCutoutMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [LayoutInDisplayCutoutMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in LayoutInDisplayCutoutMode.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -96,6 +121,17 @@ class LayoutInDisplayCutoutMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Android-specific class representing the share state that should be applied to the custom tab. @@ -168,20 +204,45 @@ class AndroidLayoutInDisplayCutoutMode { return null; } + /// Gets a possible [AndroidLayoutInDisplayCutoutMode] instance value with name [name]. + /// + /// Goes through [AndroidLayoutInDisplayCutoutMode.values] looking for a value with + /// name [name], as reported by [AndroidLayoutInDisplayCutoutMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidLayoutInDisplayCutoutMode? byName(String? name) { + if (name != null) { + try { + return AndroidLayoutInDisplayCutoutMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidLayoutInDisplayCutoutMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidLayoutInDisplayCutoutMode.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -194,4 +255,15 @@ class AndroidLayoutInDisplayCutoutMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart index 0b538034d..21c9aca15 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'loaded_resource.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart index 7949c6e57..8993e5adf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/loaded_resource.g.dart @@ -23,7 +23,8 @@ class LoadedResource { LoadedResource({this.duration, this.initiatorType, this.startTime, this.url}); ///Gets a possible [LoadedResource] instance from a [Map] value. - static LoadedResource? fromMap(Map? map) { + static LoadedResource? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -37,7 +38,7 @@ class LoadedResource { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "duration": duration, "initiatorType": initiatorType, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart index c68e58756..4f209d3c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/login_request.dart @@ -1,5 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + part 'login_request.g.dart'; ///Class used by [PlatformWebViewCreationParams.onReceivedLoginRequest] event. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart index 70436ab34..f6e2667ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/login_request.g.dart @@ -20,7 +20,8 @@ class LoginRequest { LoginRequest({this.account, required this.args, required this.realm}); ///Gets a possible [LoginRequest] instance from a [Map] value. - static LoginRequest? fromMap(Map? map) { + static LoginRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class LoginRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "account": account, "args": args, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/main.dart b/flutter_inappwebview_platform_interface/lib/src/types/main.dart index 199ebfb6a..4c4dc9149 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/main.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/main.dart @@ -59,7 +59,11 @@ export 'in_app_webview_hit_test_result_type.dart' show InAppWebViewHitTestResultType; export 'in_app_webview_initial_data.dart' show InAppWebViewInitialData; export 'in_app_webview_rect.dart' show InAppWebViewRect; -export 'javascript_handler_callback.dart' show JavaScriptHandlerCallback; +export 'javascript_handler_callback.dart' + show + JavaScriptHandlerCallback, + JavaScriptHandlerFunction, + JavaScriptHandlerFunctionData; export 'js_alert_request.dart' show JsAlertRequest; export 'js_alert_response.dart' show JsAlertResponse; export 'js_alert_response_action.dart' show JsAlertResponseAction; @@ -225,6 +229,30 @@ export 'tracing_mode.dart' show TracingMode; export 'tracing_category.dart' show TracingCategory; export 'custom_tabs_post_message_result_type.dart' show CustomTabsPostMessageResultType; -export 'custom_scheme_registration.dart' - show CustomSchemeRegistration; +export 'custom_scheme_registration.dart' show CustomSchemeRegistration; export 'disposable.dart'; +export 'frame_kind.dart' show FrameKind; +export 'process_failed_kind.dart' show ProcessFailedKind; +export 'process_failed_reason.dart' show ProcessFailedReason; +export 'process_failed_detail.dart' show ProcessFailedDetail; +export 'focus_direction.dart' show FocusDirection; +export 'enum_method.dart'; +export 'pdf_toolbar_items.dart' show PdfToolbarItems; +export 'webview_interface.dart' show WebViewInterface; +export 'download_start_response_action.dart' show DownloadStartResponseAction; +export 'download_start_response.dart' show DownloadStartResponse; +export 'environment_channel_search_kind.dart' show EnvironmentChannelSearchKind; +export 'environment_release_channels.dart' show EnvironmentReleaseChannels; +export 'environment_scrollbar_style.dart' show EnvironmentScrollbarStyle; +export 'browser_process_exit_kind.dart' show BrowserProcessExitKind; +export 'browser_process_exited_detail.dart' show BrowserProcessExitedDetail; +export 'browser_process_kind.dart' show BrowserProcessKind; +export 'browser_process_info.dart' show BrowserProcessInfo; +export 'browser_process_infos_changed_detail.dart' + show BrowserProcessInfosChangedDetail; +export 'physical_key_status.dart' show PhysicalKeyStatus; +export 'accelerator_key_pressed_detail.dart' show AcceleratorKeyPressedDetail; +export 'proxy_relay_hop.dart' show ProxyRelayHop; +export 'show_file_chooser_request_mode.dart' show ShowFileChooserRequestMode; +export 'show_file_chooser_request.dart' show ShowFileChooserRequest; +export 'show_file_chooser_response.dart' show ShowFileChooserResponse; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart index 47a0076ae..73cdcad5e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/media_capture_state.g.dart @@ -58,20 +58,44 @@ class MediaCaptureState { return null; } + /// Gets a possible [MediaCaptureState] instance value with name [name]. + /// + /// Goes through [MediaCaptureState.values] looking for a value with + /// name [name], as reported by [MediaCaptureState.name]. + /// Returns the first value with the given name, otherwise `null`. + static MediaCaptureState? byName(String? name) { + if (name != null) { + try { + return MediaCaptureState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MediaCaptureState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MediaCaptureState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ACTIVE'; @@ -82,4 +106,15 @@ class MediaCaptureState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart index a521269b6..62b8d7642 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/media_playback_state.g.dart @@ -62,20 +62,44 @@ class MediaPlaybackState { return null; } + /// Gets a possible [MediaPlaybackState] instance value with name [name]. + /// + /// Goes through [MediaPlaybackState.values] looking for a value with + /// name [name], as reported by [MediaPlaybackState.name]. + /// Returns the first value with the given name, otherwise `null`. + static MediaPlaybackState? byName(String? name) { + if (name != null) { + try { + return MediaPlaybackState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MediaPlaybackState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MediaPlaybackState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'NONE'; @@ -88,4 +112,15 @@ class MediaPlaybackState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart index 892e4688a..0d22a6604 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'meta_tag_attribute.dart'; +import 'enum_method.dart'; part 'meta_tag.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart index cbbd3505d..95029ab0d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag.g.dart @@ -19,14 +19,15 @@ class MetaTag { MetaTag({this.attrs, this.content, this.name}); ///Gets a possible [MetaTag] instance from a [Map] value. - static MetaTag? fromMap(Map? map) { + static MetaTag? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = MetaTag( attrs: map['attrs'] != null - ? List.from(map['attrs'].map( - (e) => MetaTagAttribute.fromMap(e?.cast())!)) + ? List.from(map['attrs'].map((e) => + MetaTagAttribute.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, content: map['content'], name: map['name'], @@ -35,9 +36,9 @@ class MetaTag { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "attrs": attrs?.map((e) => e.toMap()).toList(), + "attrs": attrs?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "content": content, "name": name, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart index 35048ed18..ba25c70c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'meta_tag.dart'; +import 'enum_method.dart'; part 'meta_tag_attribute.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart index 8e546a065..cd663bf8e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/meta_tag_attribute.g.dart @@ -16,7 +16,8 @@ class MetaTagAttribute { MetaTagAttribute({this.name, this.value}); ///Gets a possible [MetaTagAttribute] instance from a [Map] value. - static MetaTagAttribute? fromMap(Map? map) { + static MetaTagAttribute? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class MetaTagAttribute { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "name": name, "value": value, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart index 23cdf1436..5d75fd1b2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/mixed_content_mode.g.dart @@ -65,20 +65,44 @@ class MixedContentMode { return null; } + /// Gets a possible [MixedContentMode] instance value with name [name]. + /// + /// Goes through [MixedContentMode.values] looking for a value with + /// name [name], as reported by [MixedContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static MixedContentMode? byName(String? name) { + if (name != null) { + try { + return MixedContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [MixedContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in MixedContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'MIXED_CONTENT_ALWAYS_ALLOW'; @@ -89,6 +113,17 @@ class MixedContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the WebView's behavior when a secure origin attempts to load a resource from an insecure origin. @@ -157,20 +192,44 @@ class AndroidMixedContentMode { return null; } + /// Gets a possible [AndroidMixedContentMode] instance value with name [name]. + /// + /// Goes through [AndroidMixedContentMode.values] looking for a value with + /// name [name], as reported by [AndroidMixedContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidMixedContentMode? byName(String? name) { + if (name != null) { + try { + return AndroidMixedContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidMixedContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidMixedContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'MIXED_CONTENT_ALWAYS_ALLOW'; @@ -181,4 +240,15 @@ class AndroidMixedContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart index 71ca9419e..c784d481d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/modal_presentation_style.g.dart @@ -88,20 +88,44 @@ class ModalPresentationStyle { return null; } + /// Gets a possible [ModalPresentationStyle] instance value with name [name]. + /// + /// Goes through [ModalPresentationStyle.values] looking for a value with + /// name [name], as reported by [ModalPresentationStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ModalPresentationStyle? byName(String? name) { + if (name != null) { + try { + return ModalPresentationStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ModalPresentationStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ModalPresentationStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'AUTOMATIC'; @@ -126,6 +150,17 @@ class ModalPresentationStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to specify the modal presentation style when presenting a view controller. @@ -213,20 +248,45 @@ class IOSUIModalPresentationStyle { return null; } + /// Gets a possible [IOSUIModalPresentationStyle] instance value with name [name]. + /// + /// Goes through [IOSUIModalPresentationStyle.values] looking for a value with + /// name [name], as reported by [IOSUIModalPresentationStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIModalPresentationStyle? byName(String? name) { + if (name != null) { + try { + return IOSUIModalPresentationStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIModalPresentationStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIModalPresentationStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 9: return 'AUTOMATIC'; @@ -251,4 +311,15 @@ class IOSUIModalPresentationStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart index a5cd6ff7c..56222c794 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/modal_transition_style.g.dart @@ -68,20 +68,44 @@ class ModalTransitionStyle { return null; } + /// Gets a possible [ModalTransitionStyle] instance value with name [name]. + /// + /// Goes through [ModalTransitionStyle.values] looking for a value with + /// name [name], as reported by [ModalTransitionStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ModalTransitionStyle? byName(String? name) { + if (name != null) { + try { + return ModalTransitionStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ModalTransitionStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ModalTransitionStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'COVER_VERTICAL'; @@ -94,6 +118,17 @@ class ModalTransitionStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to specify the transition style when presenting a view controller. @@ -160,20 +195,45 @@ class IOSUIModalTransitionStyle { return null; } + /// Gets a possible [IOSUIModalTransitionStyle] instance value with name [name]. + /// + /// Goes through [IOSUIModalTransitionStyle.values] looking for a value with + /// name [name], as reported by [IOSUIModalTransitionStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIModalTransitionStyle? byName(String? name) { + if (name != null) { + try { + return IOSUIModalTransitionStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIModalTransitionStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIModalTransitionStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'COVER_VERTICAL'; @@ -186,4 +246,15 @@ class IOSUIModalTransitionStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart index cdae14782..01bad8270 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_request.dart'; import 'navigation_type.dart'; import 'frame_info.dart'; +import 'enum_method.dart'; part 'navigation_action.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart index 59b5169c4..b69730c60 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action.g.dart @@ -117,7 +117,8 @@ class NavigationAction { } ///Gets a possible [NavigationAction] instance from a [Map] value. - static NavigationAction? fromMap(Map? map) { + static NavigationAction? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -125,36 +126,55 @@ class NavigationAction { androidHasGesture: map['hasGesture'], androidIsRedirect: map['isRedirect'], hasGesture: map['hasGesture'], - iosSourceFrame: - IOSWKFrameInfo.fromMap(map['sourceFrame']?.cast()), - iosTargetFrame: - IOSWKFrameInfo.fromMap(map['targetFrame']?.cast()), - iosWKNavigationType: + iosSourceFrame: IOSWKFrameInfo.fromMap( + map['sourceFrame']?.cast(), + enumMethod: enumMethod), + iosTargetFrame: IOSWKFrameInfo.fromMap( + map['targetFrame']?.cast(), + enumMethod: enumMethod), + iosWKNavigationType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSWKNavigationType.fromNativeValue(map['navigationType']), + EnumMethod.value => + IOSWKNavigationType.fromValue(map['navigationType']), + EnumMethod.name => IOSWKNavigationType.byName(map['navigationType']) + }, isForMainFrame: map['isForMainFrame'], isRedirect: map['isRedirect'], - navigationType: NavigationType.fromNativeValue(map['navigationType']), - request: URLRequest.fromMap(map['request']?.cast())!, + navigationType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + NavigationType.fromNativeValue(map['navigationType']), + EnumMethod.value => NavigationType.fromValue(map['navigationType']), + EnumMethod.name => NavigationType.byName(map['navigationType']) + }, + request: URLRequest.fromMap(map['request']?.cast(), + enumMethod: enumMethod)!, shouldPerformDownload: map['shouldPerformDownload'], - sourceFrame: - FrameInfo.fromMap(map['sourceFrame']?.cast()), - targetFrame: - FrameInfo.fromMap(map['targetFrame']?.cast()), + sourceFrame: FrameInfo.fromMap( + map['sourceFrame']?.cast(), + enumMethod: enumMethod), + targetFrame: FrameInfo.fromMap( + map['targetFrame']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hasGesture": hasGesture, "isForMainFrame": isForMainFrame, "isRedirect": isRedirect, - "navigationType": navigationType?.toNativeValue(), - "request": request.toMap(), + "navigationType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => navigationType?.toNativeValue(), + EnumMethod.value => navigationType?.toValue(), + EnumMethod.name => navigationType?.name() + }, + "request": request.toMap(enumMethod: enumMethod), "shouldPerformDownload": shouldPerformDownload, - "sourceFrame": sourceFrame?.toMap(), - "targetFrame": targetFrame?.toMap(), + "sourceFrame": sourceFrame?.toMap(enumMethod: enumMethod), + "targetFrame": targetFrame?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart index 5bca8ddfb..90636aa90 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_action_policy.g.dart @@ -61,20 +61,44 @@ class NavigationActionPolicy { return null; } + /// Gets a possible [NavigationActionPolicy] instance value with name [name]. + /// + /// Goes through [NavigationActionPolicy.values] looking for a value with + /// name [name], as reported by [NavigationActionPolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationActionPolicy? byName(String? name) { + if (name != null) { + try { + return NavigationActionPolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [NavigationActionPolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in NavigationActionPolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ALLOW'; @@ -85,4 +109,15 @@ class NavigationActionPolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart index 0caee8019..19a99f76d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'url_response.dart'; +import 'enum_method.dart'; part 'navigation_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart index 14f5e74a8..12ef553ed 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response.g.dart @@ -22,24 +22,26 @@ class NavigationResponse { this.response}); ///Gets a possible [NavigationResponse] instance from a [Map] value. - static NavigationResponse? fromMap(Map? map) { + static NavigationResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = NavigationResponse( canShowMIMEType: map['canShowMIMEType'], isForMainFrame: map['isForMainFrame'], - response: URLResponse.fromMap(map['response']?.cast()), + response: URLResponse.fromMap(map['response']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "canShowMIMEType": canShowMIMEType, "isForMainFrame": isForMainFrame, - "response": response?.toMap(), + "response": response?.toMap(enumMethod: enumMethod), }; } @@ -72,25 +74,26 @@ class IOSWKNavigationResponse { this.response}); ///Gets a possible [IOSWKNavigationResponse] instance from a [Map] value. - static IOSWKNavigationResponse? fromMap(Map? map) { + static IOSWKNavigationResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKNavigationResponse( canShowMIMEType: map['canShowMIMEType'], isForMainFrame: map['isForMainFrame'], - response: - IOSURLResponse.fromMap(map['response']?.cast()), + response: IOSURLResponse.fromMap(map['response']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "canShowMIMEType": canShowMIMEType, "isForMainFrame": isForMainFrame, - "response": response?.toMap(), + "response": response?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart index 6c361637d..6f1e4183e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_response_action.g.dart @@ -61,20 +61,44 @@ class NavigationResponseAction { return null; } + /// Gets a possible [NavigationResponseAction] instance value with name [name]. + /// + /// Goes through [NavigationResponseAction.values] looking for a value with + /// name [name], as reported by [NavigationResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationResponseAction? byName(String? name) { + if (name != null) { + try { + return NavigationResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [NavigationResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in NavigationResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ALLOW'; @@ -85,6 +109,17 @@ class NavigationResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class that is used by [PlatformWebViewCreationParams.onNavigationResponse] event. @@ -138,12 +173,54 @@ class IOSNavigationResponseAction { return null; } + /// Gets a possible [IOSNavigationResponseAction] instance value with name [name]. + /// + /// Goes through [IOSNavigationResponseAction.values] looking for a value with + /// name [name], as reported by [IOSNavigationResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNavigationResponseAction? byName(String? name) { + if (name != null) { + try { + return IOSNavigationResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNavigationResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNavigationResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -152,12 +229,6 @@ class IOSNavigationResponseAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart index 7615cd66f..4954e7c7b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/navigation_type.g.dart @@ -170,12 +170,60 @@ class NavigationType { return null; } + /// Gets a possible [NavigationType] instance value with name [name]. + /// + /// Goes through [NavigationType.values] looking for a value with + /// name [name], as reported by [NavigationType.name]. + /// Returns the first value with the given name, otherwise `null`. + static NavigationType? byName(String? name) { + if (name != null) { + try { + return NavigationType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [NavigationType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in NavigationType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'BACK_FORWARD': + return 'BACK_FORWARD'; + case 'FORM_RESUBMITTED': + return 'FORM_RESUBMITTED'; + case 'FORM_SUBMITTED': + return 'FORM_SUBMITTED'; + case 'LINK_ACTIVATED': + return 'LINK_ACTIVATED'; + case 'OTHER': + return 'OTHER'; + case 'RELOAD': + return 'RELOAD'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -254,20 +302,44 @@ class IOSWKNavigationType { return null; } + /// Gets a possible [IOSWKNavigationType] instance value with name [name]. + /// + /// Goes through [IOSWKNavigationType.values] looking for a value with + /// name [name], as reported by [IOSWKNavigationType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKNavigationType? byName(String? name) { + if (name != null) { + try { + return IOSWKNavigationType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKNavigationType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKNavigationType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 2: return 'BACK_FORWARD'; @@ -284,4 +356,15 @@ class IOSWKNavigationType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart index 7e4b5e432..228e28b4f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/over_scroll_mode.g.dart @@ -59,20 +59,43 @@ class OverScrollMode { return null; } + /// Gets a possible [OverScrollMode] instance value with name [name]. + /// + /// Goes through [OverScrollMode.values] looking for a value with + /// name [name], as reported by [OverScrollMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static OverScrollMode? byName(String? name) { + if (name != null) { + try { + return OverScrollMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [OverScrollMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in OverScrollMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'ALWAYS'; @@ -83,6 +106,17 @@ class OverScrollMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the `WebView`'s over-scroll mode. @@ -141,20 +175,44 @@ class AndroidOverScrollMode { return null; } + /// Gets a possible [AndroidOverScrollMode] instance value with name [name]. + /// + /// Goes through [AndroidOverScrollMode.values] looking for a value with + /// name [name], as reported by [AndroidOverScrollMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidOverScrollMode? byName(String? name) { + if (name != null) { + try { + return AndroidOverScrollMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidOverScrollMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidOverScrollMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'OVER_SCROLL_ALWAYS'; @@ -165,4 +223,15 @@ class AndroidOverScrollMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart index 20e52f937..e807960ec 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'in_app_webview_rect.dart'; +import 'enum_method.dart'; part 'pdf_configuration.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart index b4c069453..ed54234d3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_configuration.g.dart @@ -15,20 +15,22 @@ class PDFConfiguration { PDFConfiguration({this.rect}); ///Gets a possible [PDFConfiguration] instance from a [Map] value. - static PDFConfiguration? fromMap(Map? map) { + static PDFConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PDFConfiguration( - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), }; } @@ -57,20 +59,22 @@ class IOSWKPDFConfiguration { IOSWKPDFConfiguration({this.rect}); ///Gets a possible [IOSWKPDFConfiguration] instance from a [Map] value. - static IOSWKPDFConfiguration? fromMap(Map? map) { + static IOSWKPDFConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKPDFConfiguration( - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart new file mode 100644 index 000000000..c6f346e4e --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.dart @@ -0,0 +1,53 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'pdf_toolbar_items.g.dart'; + +///Class used to customize the PDF toolbar items. +@ExchangeableEnum(bitwiseOrOperator: true) +class PdfToolbarItems_ { + // ignore: unused_field + final int _value; + const PdfToolbarItems_._internal(this._value); + + ///No item. + static const NONE = const PdfToolbarItems_._internal(0); + + ///The save button. + static const SAVE = const PdfToolbarItems_._internal(1); + + ///The print button. + static const PRINT = const PdfToolbarItems_._internal(2); + + ///The save as button. + static const SAVE_AS = const PdfToolbarItems_._internal(4); + + ///The zoom in button. + static const ZOOM_IN = const PdfToolbarItems_._internal(8); + + ///The zoom out button. + static const ZOOM_OUT = const PdfToolbarItems_._internal(16); + + ///The rotate button. + static const ROTATE = const PdfToolbarItems_._internal(32); + + ///The fit page button. + static const FIT_PAGE = const PdfToolbarItems_._internal(64); + + ///The page layout button. + static const PAGE_LAYOUT = const PdfToolbarItems_._internal(128); + + ///The bookmarks button. + static const BOOKMARKS = const PdfToolbarItems_._internal(256); + + ///The page select button. + static const PAGE_SELECTOR = const PdfToolbarItems_._internal(512); + + ///The search button. + static const SEARCH = const PdfToolbarItems_._internal(1024); + + ///The full screen button. + static const FULL_SCREEN = const PdfToolbarItems_._internal(2048); + + ///The more settings button. + static const MORE_SETTINGS = const PdfToolbarItems_._internal(4096); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart new file mode 100644 index 000000000..5a9fdc1e5 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/pdf_toolbar_items.g.dart @@ -0,0 +1,188 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pdf_toolbar_items.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to customize the PDF toolbar items. +class PdfToolbarItems { + final int _value; + final int _nativeValue; + const PdfToolbarItems._internal(this._value, this._nativeValue); +// ignore: unused_element + factory PdfToolbarItems._internalMultiPlatform( + int value, Function nativeValue) => + PdfToolbarItems._internal(value, nativeValue()); + + ///The bookmarks button. + static const BOOKMARKS = PdfToolbarItems._internal(256, 256); + + ///The fit page button. + static const FIT_PAGE = PdfToolbarItems._internal(64, 64); + + ///The full screen button. + static const FULL_SCREEN = PdfToolbarItems._internal(2048, 2048); + + ///The more settings button. + static const MORE_SETTINGS = PdfToolbarItems._internal(4096, 4096); + + ///No item. + static const NONE = PdfToolbarItems._internal(0, 0); + + ///The page layout button. + static const PAGE_LAYOUT = PdfToolbarItems._internal(128, 128); + + ///The page select button. + static const PAGE_SELECTOR = PdfToolbarItems._internal(512, 512); + + ///The print button. + static const PRINT = PdfToolbarItems._internal(2, 2); + + ///The rotate button. + static const ROTATE = PdfToolbarItems._internal(32, 32); + + ///The save button. + static const SAVE = PdfToolbarItems._internal(1, 1); + + ///The save as button. + static const SAVE_AS = PdfToolbarItems._internal(4, 4); + + ///The search button. + static const SEARCH = PdfToolbarItems._internal(1024, 1024); + + ///The zoom in button. + static const ZOOM_IN = PdfToolbarItems._internal(8, 8); + + ///The zoom out button. + static const ZOOM_OUT = PdfToolbarItems._internal(16, 16); + + ///Set of all values of [PdfToolbarItems]. + static final Set values = [ + PdfToolbarItems.BOOKMARKS, + PdfToolbarItems.FIT_PAGE, + PdfToolbarItems.FULL_SCREEN, + PdfToolbarItems.MORE_SETTINGS, + PdfToolbarItems.NONE, + PdfToolbarItems.PAGE_LAYOUT, + PdfToolbarItems.PAGE_SELECTOR, + PdfToolbarItems.PRINT, + PdfToolbarItems.ROTATE, + PdfToolbarItems.SAVE, + PdfToolbarItems.SAVE_AS, + PdfToolbarItems.SEARCH, + PdfToolbarItems.ZOOM_IN, + PdfToolbarItems.ZOOM_OUT, + ].toSet(); + + ///Gets a possible [PdfToolbarItems] instance from [int] value. + static PdfToolbarItems? fromValue(int? value) { + if (value != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return PdfToolbarItems._internal(value, value); + } + } + return null; + } + + ///Gets a possible [PdfToolbarItems] instance from a native value. + static PdfToolbarItems? fromNativeValue(int? value) { + if (value != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return PdfToolbarItems._internal(value, value); + } + } + return null; + } + + /// Gets a possible [PdfToolbarItems] instance value with name [name]. + /// + /// Goes through [PdfToolbarItems.values] looking for a value with + /// name [name], as reported by [PdfToolbarItems.name]. + /// Returns the first value with the given name, otherwise `null`. + static PdfToolbarItems? byName(String? name) { + if (name != null) { + try { + return PdfToolbarItems.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PdfToolbarItems] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in PdfToolbarItems.values) value.name(): value + }; + + ///Gets [int] value. + int toValue() => _value; + + ///Gets [int] native value. + int toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 256: + return 'BOOKMARKS'; + case 64: + return 'FIT_PAGE'; + case 2048: + return 'FULL_SCREEN'; + case 4096: + return 'MORE_SETTINGS'; + case 0: + return 'NONE'; + case 128: + return 'PAGE_LAYOUT'; + case 512: + return 'PAGE_SELECTOR'; + case 2: + return 'PRINT'; + case 32: + return 'ROTATE'; + case 1: + return 'SAVE'; + case 4: + return 'SAVE_AS'; + case 1024: + return 'SEARCH'; + case 8: + return 'ZOOM_IN'; + case 16: + return 'ZOOM_OUT'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + PdfToolbarItems operator |(PdfToolbarItems value) => + PdfToolbarItems._internal( + value.toValue() | _value, value.toNativeValue() | _nativeValue); + @override + String toString() { + return name(); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart index e5588ef76..dc0ca59cf 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.dart @@ -1,9 +1,11 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import '../web_uri.dart'; import 'permission_resource_type.dart'; import 'permission_response.dart'; import 'frame_info.dart'; +import 'enum_method.dart'; part 'permission_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart index a52ea4d80..d2061f1c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_request.g.dart @@ -23,25 +23,40 @@ class PermissionRequest { {this.frame, required this.origin, this.resources = const []}); ///Gets a possible [PermissionRequest] instance from a [Map] value. - static PermissionRequest? fromMap(Map? map) { + static PermissionRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionRequest( - frame: FrameInfo.fromMap(map['frame']?.cast()), + frame: FrameInfo.fromMap(map['frame']?.cast(), + enumMethod: enumMethod), origin: WebUri(map['origin']), ); - instance.resources = List.from(map['resources'] - .map((e) => PermissionResourceType.fromNativeValue(e)!)); + if (map['resources'] != null) { + instance.resources = List.from(map['resources'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResourceType.fromNativeValue(e), + EnumMethod.value => PermissionResourceType.fromValue(e), + EnumMethod.name => PermissionResourceType.byName(e) + }!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "frame": frame?.toMap(), + "frame": frame?.toMap(enumMethod: enumMethod), "origin": origin.toString(), - "resources": resources.map((e) => e.toNativeValue()).toList(), + "resources": resources + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart index 80b482269..3ab4ae8a1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_resource_type.g.dart @@ -346,12 +346,81 @@ class PermissionResourceType { return null; } + /// Gets a possible [PermissionResourceType] instance value with name [name]. + /// + /// Goes through [PermissionResourceType.values] looking for a value with + /// name [name], as reported by [PermissionResourceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionResourceType? byName(String? name) { + if (name != null) { + try { + return PermissionResourceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionResourceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionResourceType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [dynamic] native value. dynamic toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'AUTOPLAY': + return 'AUTOPLAY'; + case 'CAMERA': + return 'CAMERA'; + case 'CAMERA_AND_MICROPHONE': + return 'CAMERA_AND_MICROPHONE'; + case 'CLIPBOARD_READ': + return 'CLIPBOARD_READ'; + case 'DEVICE_ORIENTATION_AND_MOTION': + return 'DEVICE_ORIENTATION_AND_MOTION'; + case 'FILE_READ_WRITE': + return 'FILE_READ_WRITE'; + case 'GEOLOCATION': + return 'GEOLOCATION'; + case 'LOCAL_FONTS': + return 'LOCAL_FONTS'; + case 'MICROPHONE': + return 'MICROPHONE'; + case 'MIDI_SYSEX': + return 'MIDI_SYSEX'; + case 'MULTIPLE_AUTOMATIC_DOWNLOADS': + return 'MULTIPLE_AUTOMATIC_DOWNLOADS'; + case 'NOTIFICATIONS': + return 'NOTIFICATIONS'; + case 'OTHER_SENSORS': + return 'OTHER_SENSORS'; + case 'PROTECTED_MEDIA_ID': + return 'PROTECTED_MEDIA_ID'; + case 'UNKNOWN': + return 'UNKNOWN'; + case 'WINDOW_MANAGEMENT': + return 'WINDOW_MANAGEMENT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart index 453b80063..39b7d6457 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.dart @@ -1,7 +1,9 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'permission_resource_type.dart'; import 'permission_response_action.dart'; +import 'enum_method.dart'; part 'permission_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart index 1cd597f28..296ae8548 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response.g.dart @@ -19,22 +19,45 @@ class PermissionResponse { {this.action = PermissionResponseAction.DENY, this.resources = const []}); ///Gets a possible [PermissionResponse] instance from a [Map] value. - static PermissionResponse? fromMap(Map? map) { + static PermissionResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionResponse(); - instance.action = PermissionResponseAction.fromNativeValue(map['action']); - instance.resources = List.from(map['resources'] - .map((e) => PermissionResourceType.fromNativeValue(e)!)); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResponseAction.fromNativeValue(map['action']), + EnumMethod.value => PermissionResponseAction.fromValue(map['action']), + EnumMethod.name => PermissionResponseAction.byName(map['action']) + }; + if (map['resources'] != null) { + instance.resources = List.from(map['resources'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionResourceType.fromNativeValue(e), + EnumMethod.value => PermissionResourceType.fromValue(e), + EnumMethod.name => PermissionResourceType.byName(e) + }!)); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), - "resources": resources.map((e) => e.toNativeValue()).toList(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, + "resources": resources + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), }; } @@ -63,21 +86,33 @@ class PermissionRequestResponse { this.resources = const []}); ///Gets a possible [PermissionRequestResponse] instance from a [Map] value. - static PermissionRequestResponse? fromMap(Map? map) { + static PermissionRequestResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PermissionRequestResponse(); - instance.action = - PermissionRequestResponseAction.fromNativeValue(map['action']); - instance.resources = List.from(map['resources']!.cast()); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PermissionRequestResponseAction.fromNativeValue(map['action']), + EnumMethod.value => + PermissionRequestResponseAction.fromValue(map['action']), + EnumMethod.name => PermissionRequestResponseAction.byName(map['action']) + }; + if (map['resources'] != null) { + instance.resources = List.from(map['resources']!.cast()); + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "resources": resources, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart index 33c5e4964..2dbce8e18 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/permission_response_action.g.dart @@ -58,20 +58,44 @@ class PermissionResponseAction { return null; } + /// Gets a possible [PermissionResponseAction] instance value with name [name]. + /// + /// Goes through [PermissionResponseAction.values] looking for a value with + /// name [name], as reported by [PermissionResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionResponseAction? byName(String? name) { + if (name != null) { + try { + return PermissionResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionResponseAction.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'DENY'; @@ -82,6 +106,17 @@ class PermissionResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class used by [PermissionRequestResponse] class. @@ -135,12 +170,54 @@ class PermissionRequestResponseAction { return null; } + /// Gets a possible [PermissionRequestResponseAction] instance value with name [name]. + /// + /// Goes through [PermissionRequestResponseAction.values] looking for a value with + /// name [name], as reported by [PermissionRequestResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static PermissionRequestResponseAction? byName(String? name) { + if (name != null) { + try { + return PermissionRequestResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PermissionRequestResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PermissionRequestResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'DENY'; + case 1: + return 'GRANT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -149,12 +226,6 @@ class PermissionRequestResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'DENY'; - case 1: - return 'GRANT'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart new file mode 100644 index 000000000..4177c4ab0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.dart @@ -0,0 +1,37 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'enum_method.dart'; + +part 'physical_key_status.g.dart'; + +///Contains the information packed into the LPARAM sent to a Win32 key event. +@ExchangeableObject() +class PhysicalKeyStatus_ { + ///Indicates that the key is an extended key. + bool isExtendedKey; + + ///Indicates that the key was released. + bool isKeyReleased; + + ///Indicates that a menu key is held down (context code). + bool isMenuKeyDown; + + ///Specifies the repeat count for the current message. + int repeatCount; + + ///Specifies the scan code. + int scanCode; + + ///Indicates that the key was held down. + bool wasKeyDown; + + @ExchangeableObjectConstructor() + PhysicalKeyStatus_({ + required this.isExtendedKey, + required this.isKeyReleased, + required this.isMenuKeyDown, + required this.repeatCount, + required this.scanCode, + required this.wasKeyDown, + }); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart new file mode 100644 index 000000000..a655c26e0 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/physical_key_status.g.dart @@ -0,0 +1,74 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'physical_key_status.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Contains the information packed into the LPARAM sent to a Win32 key event. +class PhysicalKeyStatus { + ///Indicates that the key is an extended key. + bool isExtendedKey; + + ///Indicates that the key was released. + bool isKeyReleased; + + ///Indicates that a menu key is held down (context code). + bool isMenuKeyDown; + + ///Specifies the repeat count for the current message. + int repeatCount; + + ///Specifies the scan code. + int scanCode; + + ///Indicates that the key was held down. + bool wasKeyDown; + PhysicalKeyStatus( + {required this.isExtendedKey, + required this.isKeyReleased, + required this.isMenuKeyDown, + required this.repeatCount, + required this.scanCode, + required this.wasKeyDown}); + + ///Gets a possible [PhysicalKeyStatus] instance from a [Map] value. + static PhysicalKeyStatus? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = PhysicalKeyStatus( + isExtendedKey: map['isExtendedKey'], + isKeyReleased: map['isKeyReleased'], + isMenuKeyDown: map['isMenuKeyDown'], + repeatCount: map['repeatCount'], + scanCode: map['scanCode'], + wasKeyDown: map['wasKeyDown'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "isExtendedKey": isExtendedKey, + "isKeyReleased": isKeyReleased, + "isMenuKeyDown": isMenuKeyDown, + "repeatCount": repeatCount, + "scanCode": scanCode, + "wasKeyDown": wasKeyDown, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'PhysicalKeyStatus{isExtendedKey: $isExtendedKey, isKeyReleased: $isKeyReleased, isMenuKeyDown: $isMenuKeyDown, repeatCount: $repeatCount, scanCode: $scanCode, wasKeyDown: $wasKeyDown}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart index 583ba2b94..cc460b81c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../chrome_safari_browser/platform_chrome_safari_browser.dart'; +import 'enum_method.dart'; part 'prewarming_token.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart index 334482b8b..bec21040d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/prewarming_token.g.dart @@ -13,7 +13,8 @@ class PrewarmingToken { PrewarmingToken({required this.id}); ///Gets a possible [PrewarmingToken] instance from a [Map] value. - static PrewarmingToken? fromMap(Map? map) { + static PrewarmingToken? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -24,7 +25,7 @@ class PrewarmingToken { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart index db5c09244..c108314e1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.dart @@ -1,17 +1,18 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; -import '../util.dart'; import '../print_job/main.dart'; +import '../util.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; import 'in_app_webview_rect.dart'; import 'print_job_color_mode.dart'; +import 'print_job_disposition.dart'; import 'print_job_duplex_mode.dart'; -import 'print_job_orientation.dart'; import 'print_job_media_size.dart'; -import 'print_job_resolution.dart'; +import 'print_job_orientation.dart'; import 'print_job_pagination_mode.dart'; -import 'print_job_disposition.dart'; +import 'print_job_resolution.dart'; part 'print_job_attributes.g.dart'; @@ -32,6 +33,11 @@ class PrintJobAttributes_ { PrintJobDuplexMode_? duplex; ///The orientation of the printed content, portrait or landscape. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + ]) PrintJobOrientation_? orientation; ///The media size. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart index 6c9a99cf3..90385bd45 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_attributes.g.dart @@ -145,6 +145,11 @@ class PrintJobAttributes { bool? mustCollate; ///The orientation of the printed content, portrait or landscape. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS PrintJobOrientation? orientation; ///The number of logical pages to be tiled horizontally on a physical sheet of paper. @@ -240,84 +245,145 @@ class PrintJobAttributes { this.verticalPagination}); ///Gets a possible [PrintJobAttributes] instance from a [Map] value. - static PrintJobAttributes? fromMap(Map? map) { + static PrintJobAttributes? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PrintJobAttributes( - colorMode: PrintJobColorMode.fromNativeValue(map['colorMode']), + colorMode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobColorMode.fromNativeValue(map['colorMode']), + EnumMethod.value => PrintJobColorMode.fromValue(map['colorMode']), + EnumMethod.name => PrintJobColorMode.byName(map['colorMode']) + }, detailedErrorReporting: map['detailedErrorReporting'], - duplex: PrintJobDuplexMode.fromNativeValue(map['duplex']), + duplex: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobDuplexMode.fromNativeValue(map['duplex']), + EnumMethod.value => PrintJobDuplexMode.fromValue(map['duplex']), + EnumMethod.name => PrintJobDuplexMode.byName(map['duplex']) + }, faxNumber: map['faxNumber'], footerHeight: map['footerHeight'], headerAndFooter: map['headerAndFooter'], headerHeight: map['headerHeight'], - horizontalPagination: + horizontalPagination: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobPaginationMode.fromNativeValue(map['horizontalPagination']), + EnumMethod.value => + PrintJobPaginationMode.fromValue(map['horizontalPagination']), + EnumMethod.name => + PrintJobPaginationMode.byName(map['horizontalPagination']) + }, isHorizontallyCentered: map['isHorizontallyCentered'], isSelectionOnly: map['isSelectionOnly'], isVerticallyCentered: map['isVerticallyCentered'], - jobDisposition: + jobDisposition: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobDisposition.fromNativeValue(map['jobDisposition']), + EnumMethod.value => + PrintJobDisposition.fromValue(map['jobDisposition']), + EnumMethod.name => PrintJobDisposition.byName(map['jobDisposition']) + }, jobSavingURL: map['jobSavingURL'] != null ? WebUri(map['jobSavingURL']) : null, localizedPaperName: map['localizedPaperName'], margins: MapEdgeInsets.fromMap(map['margins']?.cast()), maximumContentHeight: map['maximumContentHeight'], maximumContentWidth: map['maximumContentWidth'], - mediaSize: - PrintJobMediaSize.fromMap(map['mediaSize']?.cast()), + mediaSize: PrintJobMediaSize.fromMap( + map['mediaSize']?.cast(), + enumMethod: enumMethod), mustCollate: map['mustCollate'], - orientation: PrintJobOrientation.fromNativeValue(map['orientation']), + orientation: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobOrientation.fromNativeValue(map['orientation']), + EnumMethod.value => PrintJobOrientation.fromValue(map['orientation']), + EnumMethod.name => PrintJobOrientation.byName(map['orientation']) + }, pagesAcross: map['pagesAcross'], pagesDown: map['pagesDown'], paperName: map['paperName'], - paperRect: - InAppWebViewRect.fromMap(map['paperRect']?.cast()), + paperRect: InAppWebViewRect.fromMap( + map['paperRect']?.cast(), + enumMethod: enumMethod), printableRect: InAppWebViewRect.fromMap( - map['printableRect']?.cast()), + map['printableRect']?.cast(), + enumMethod: enumMethod), resolution: PrintJobResolution.fromMap( - map['resolution']?.cast()), + map['resolution']?.cast(), + enumMethod: enumMethod), scalingFactor: map['scalingFactor'], time: map['time'], - verticalPagination: + verticalPagination: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobPaginationMode.fromNativeValue(map['verticalPagination']), + EnumMethod.value => + PrintJobPaginationMode.fromValue(map['verticalPagination']), + EnumMethod.name => + PrintJobPaginationMode.byName(map['verticalPagination']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "colorMode": colorMode?.toNativeValue(), + "colorMode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => colorMode?.toNativeValue(), + EnumMethod.value => colorMode?.toValue(), + EnumMethod.name => colorMode?.name() + }, "detailedErrorReporting": detailedErrorReporting, - "duplex": duplex?.toNativeValue(), + "duplex": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => duplex?.toNativeValue(), + EnumMethod.value => duplex?.toValue(), + EnumMethod.name => duplex?.name() + }, "faxNumber": faxNumber, "footerHeight": footerHeight, "headerAndFooter": headerAndFooter, "headerHeight": headerHeight, - "horizontalPagination": horizontalPagination?.toNativeValue(), + "horizontalPagination": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => horizontalPagination?.toNativeValue(), + EnumMethod.value => horizontalPagination?.toValue(), + EnumMethod.name => horizontalPagination?.name() + }, "isHorizontallyCentered": isHorizontallyCentered, "isSelectionOnly": isSelectionOnly, "isVerticallyCentered": isVerticallyCentered, - "jobDisposition": jobDisposition?.toNativeValue(), + "jobDisposition": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => jobDisposition?.toNativeValue(), + EnumMethod.value => jobDisposition?.toValue(), + EnumMethod.name => jobDisposition?.name() + }, "jobSavingURL": jobSavingURL?.toString(), "localizedPaperName": localizedPaperName, "margins": margins?.toMap(), "maximumContentHeight": maximumContentHeight, "maximumContentWidth": maximumContentWidth, - "mediaSize": mediaSize?.toMap(), + "mediaSize": mediaSize?.toMap(enumMethod: enumMethod), "mustCollate": mustCollate, - "orientation": orientation?.toNativeValue(), + "orientation": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => orientation?.toNativeValue(), + EnumMethod.value => orientation?.toValue(), + EnumMethod.name => orientation?.name() + }, "pagesAcross": pagesAcross, "pagesDown": pagesDown, "paperName": paperName, - "paperRect": paperRect?.toMap(), - "printableRect": printableRect?.toMap(), - "resolution": resolution?.toMap(), + "paperRect": paperRect?.toMap(enumMethod: enumMethod), + "printableRect": printableRect?.toMap(enumMethod: enumMethod), + "resolution": resolution?.toMap(enumMethod: enumMethod), "scalingFactor": scalingFactor, "time": time, - "verticalPagination": verticalPagination?.toNativeValue(), + "verticalPagination": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => verticalPagination?.toNativeValue(), + EnumMethod.value => verticalPagination?.toValue(), + EnumMethod.name => verticalPagination?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart index 4bd1478f0..471ec4619 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_color_mode.g.dart @@ -82,12 +82,53 @@ class PrintJobColorMode { return null; } + /// Gets a possible [PrintJobColorMode] instance value with name [name]. + /// + /// Goes through [PrintJobColorMode.values] looking for a value with + /// name [name], as reported by [PrintJobColorMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobColorMode? byName(String? name) { + if (name != null) { + try { + return PrintJobColorMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobColorMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobColorMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [dynamic] native value. dynamic toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 2: + return 'COLOR'; + case 1: + return 'MONOCHROME'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -96,12 +137,6 @@ class PrintJobColorMode { @override String toString() { - switch (_value) { - case 2: - return 'COLOR'; - case 1: - return 'MONOCHROME'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart index 65f090465..14c5a01e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_disposition.g.dart @@ -108,12 +108,57 @@ class PrintJobDisposition { return null; } + /// Gets a possible [PrintJobDisposition] instance value with name [name]. + /// + /// Goes through [PrintJobDisposition.values] looking for a value with + /// name [name], as reported by [PrintJobDisposition.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobDisposition? byName(String? name) { + if (name != null) { + try { + return PrintJobDisposition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobDisposition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobDisposition.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CANCEL': + return 'CANCEL'; + case 'PREVIEW': + return 'PREVIEW'; + case 'SAVE': + return 'SAVE'; + case 'SPOOL': + return 'SPOOL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart index fb607beca..0ee6d2811 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_duplex_mode.g.dart @@ -113,12 +113,55 @@ class PrintJobDuplexMode { return null; } + /// Gets a possible [PrintJobDuplexMode] instance value with name [name]. + /// + /// Goes through [PrintJobDuplexMode.values] looking for a value with + /// name [name], as reported by [PrintJobDuplexMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobDuplexMode? byName(String? name) { + if (name != null) { + try { + return PrintJobDuplexMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobDuplexMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobDuplexMode.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'LONG_EDGE': + return 'LONG_EDGE'; + case 'NONE': + return 'NONE'; + case 'SHORT_EDGE': + return 'SHORT_EDGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart index 6e9c16a69..eaf4fee13 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.dart @@ -6,6 +6,7 @@ import 'print_job_rendering_quality.dart'; import 'print_job_state.dart'; import 'print_job_page_order.dart'; import 'printer.dart'; +import 'enum_method.dart'; part 'print_job_info.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart index f65ecd164..c24a74ff8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_info.g.dart @@ -139,13 +139,15 @@ class PrintJobInfo { this.state}); ///Gets a possible [PrintJobInfo] instance from a [Map] value. - static PrintJobInfo? fromMap(Map? map) { + static PrintJobInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = PrintJobInfo( attributes: PrintJobAttributes.fromMap( - map['attributes']?.cast()), + map['attributes']?.cast(), + enumMethod: enumMethod), canSpawnSeparateThread: map['canSpawnSeparateThread'], copies: map['copies'], creationTime: map['creationTime'], @@ -155,21 +157,37 @@ class PrintJobInfo { label: map['label'], lastPage: map['lastPage'], numberOfPages: map['numberOfPages'], - pageOrder: PrintJobPageOrder.fromNativeValue(map['pageOrder']), - preferredRenderingQuality: PrintJobRenderingQuality.fromNativeValue( - map['preferredRenderingQuality']), - printer: Printer.fromMap(map['printer']?.cast()), + pageOrder: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + PrintJobPageOrder.fromNativeValue(map['pageOrder']), + EnumMethod.value => PrintJobPageOrder.fromValue(map['pageOrder']), + EnumMethod.name => PrintJobPageOrder.byName(map['pageOrder']) + }, + preferredRenderingQuality: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobRenderingQuality.fromNativeValue( + map['preferredRenderingQuality']), + EnumMethod.value => + PrintJobRenderingQuality.fromValue(map['preferredRenderingQuality']), + EnumMethod.name => + PrintJobRenderingQuality.byName(map['preferredRenderingQuality']) + }, + printer: Printer.fromMap(map['printer']?.cast(), + enumMethod: enumMethod), showsPrintPanel: map['showsPrintPanel'], showsProgressPanel: map['showsProgressPanel'], - state: PrintJobState.fromNativeValue(map['state']), + state: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => PrintJobState.fromNativeValue(map['state']), + EnumMethod.value => PrintJobState.fromValue(map['state']), + EnumMethod.name => PrintJobState.byName(map['state']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "attributes": attributes?.toMap(), + "attributes": attributes?.toMap(enumMethod: enumMethod), "canSpawnSeparateThread": canSpawnSeparateThread, "copies": copies, "creationTime": creationTime, @@ -179,12 +197,25 @@ class PrintJobInfo { "label": label, "lastPage": lastPage, "numberOfPages": numberOfPages, - "pageOrder": pageOrder?.toNativeValue(), - "preferredRenderingQuality": preferredRenderingQuality?.toNativeValue(), - "printer": printer?.toMap(), + "pageOrder": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => pageOrder?.toNativeValue(), + EnumMethod.value => pageOrder?.toValue(), + EnumMethod.name => pageOrder?.name() + }, + "preferredRenderingQuality": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => preferredRenderingQuality?.toNativeValue(), + EnumMethod.value => preferredRenderingQuality?.toValue(), + EnumMethod.name => preferredRenderingQuality?.name() + }, + "printer": printer?.toMap(enumMethod: enumMethod), "showsPrintPanel": showsPrintPanel, "showsProgressPanel": showsProgressPanel, - "state": state?.toNativeValue(), + "state": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => state?.toNativeValue(), + EnumMethod.value => state?.toValue(), + EnumMethod.name => state?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart index 828f86609..da7fc0c0b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'print_job_media_size.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart index 038449c13..aabb967c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_media_size.g.dart @@ -417,7 +417,8 @@ class PrintJobMediaSize { required this.widthMils}); ///Gets a possible [PrintJobMediaSize] instance from a [Map] value. - static PrintJobMediaSize? fromMap(Map? map) { + static PrintJobMediaSize? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -431,7 +432,7 @@ class PrintJobMediaSize { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "heightMils": heightMils, "id": id, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart index 259125ffc..2080141fe 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.dart @@ -13,12 +13,18 @@ class PrintJobOrientation_ { const PrintJobOrientation_._internal(this._value); ///Pages are printed in portrait orientation. - @EnumSupportedPlatforms( - platforms: [EnumIOSPlatform(value: 0), EnumMacOSPlatform(value: 0)]) + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 0), + EnumIOSPlatform(value: 0), + EnumMacOSPlatform(value: 0), + ]) static const PORTRAIT = const PrintJobOrientation_._internal(0); ///Pages are printed in landscape orientation. - @EnumSupportedPlatforms( - platforms: [EnumIOSPlatform(value: 1), EnumMacOSPlatform(value: 1)]) + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 1), + EnumIOSPlatform(value: 1), + EnumMacOSPlatform(value: 1), + ]) static const LANDSCAPE = const PrintJobOrientation_._internal(1); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart index 334282859..15cda90d4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_orientation.g.dart @@ -19,10 +19,13 @@ class PrintJobOrientation { ///Pages are printed in landscape orientation. /// ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ///- iOS ///- MacOS static final LANDSCAPE = PrintJobOrientation._internalMultiPlatform(1, () { switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 1; case TargetPlatform.iOS: return 1; case TargetPlatform.macOS: @@ -36,10 +39,13 @@ class PrintJobOrientation { ///Pages are printed in portrait orientation. /// ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ///- iOS ///- MacOS static final PORTRAIT = PrintJobOrientation._internalMultiPlatform(0, () { switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 0; case TargetPlatform.iOS: return 0; case TargetPlatform.macOS: @@ -82,12 +88,53 @@ class PrintJobOrientation { return null; } + /// Gets a possible [PrintJobOrientation] instance value with name [name]. + /// + /// Goes through [PrintJobOrientation.values] looking for a value with + /// name [name], as reported by [PrintJobOrientation.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobOrientation? byName(String? name) { + if (name != null) { + try { + return PrintJobOrientation.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobOrientation] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobOrientation.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'LANDSCAPE'; + case 0: + return 'PORTRAIT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -96,12 +143,6 @@ class PrintJobOrientation { @override String toString() { - switch (_value) { - case 1: - return 'LANDSCAPE'; - case 0: - return 'PORTRAIT'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart index c4b7ca7ff..a4d7a9945 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_output_type.g.dart @@ -72,20 +72,44 @@ class PrintJobOutputType { return null; } + /// Gets a possible [PrintJobOutputType] instance value with name [name]. + /// + /// Goes through [PrintJobOutputType.values] looking for a value with + /// name [name], as reported by [PrintJobOutputType.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobOutputType? byName(String? name) { + if (name != null) { + try { + return PrintJobOutputType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobOutputType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobOutputType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'GENERAL'; @@ -98,4 +122,15 @@ class PrintJobOutputType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart index bcbee904a..2274d4155 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_page_order.g.dart @@ -106,20 +106,44 @@ class PrintJobPageOrder { return null; } + /// Gets a possible [PrintJobPageOrder] instance value with name [name]. + /// + /// Goes through [PrintJobPageOrder.values] looking for a value with + /// name [name], as reported by [PrintJobPageOrder.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobPageOrder? byName(String? name) { + if (name != null) { + try { + return PrintJobPageOrder.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobPageOrder] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobPageOrder.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'ASCENDING'; @@ -132,4 +156,15 @@ class PrintJobPageOrder { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart index ec1da7bb5..ac7bf9986 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_pagination_mode.g.dart @@ -92,12 +92,55 @@ class PrintJobPaginationMode { return null; } + /// Gets a possible [PrintJobPaginationMode] instance value with name [name]. + /// + /// Goes through [PrintJobPaginationMode.values] looking for a value with + /// name [name], as reported by [PrintJobPaginationMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobPaginationMode? byName(String? name) { + if (name != null) { + try { + return PrintJobPaginationMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobPaginationMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobPaginationMode.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'AUTOMATIC': + return 'AUTOMATIC'; + case 'CLIP': + return 'CLIP'; + case 'FIT': + return 'FIT'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart index 70af46e3a..0a4783cd8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_rendering_quality.g.dart @@ -84,12 +84,53 @@ class PrintJobRenderingQuality { return null; } + /// Gets a possible [PrintJobRenderingQuality] instance value with name [name]. + /// + /// Goes through [PrintJobRenderingQuality.values] looking for a value with + /// name [name], as reported by [PrintJobRenderingQuality.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobRenderingQuality? byName(String? name) { + if (name != null) { + try { + return PrintJobRenderingQuality.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobRenderingQuality] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PrintJobRenderingQuality.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'BEST'; + case 1: + return 'RESPONSIVE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -98,12 +139,6 @@ class PrintJobRenderingQuality { @override String toString() { - switch (_value) { - case 0: - return 'BEST'; - case 1: - return 'RESPONSIVE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart index cbbfd3c0f..f2ea665c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'print_job_resolution.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart index d5827269e..21a003182 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_resolution.g.dart @@ -34,7 +34,8 @@ class PrintJobResolution { required this.verticalDpi}); ///Gets a possible [PrintJobResolution] instance from a [Map] value. - static PrintJobResolution? fromMap(Map? map) { + static PrintJobResolution? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -48,7 +49,7 @@ class PrintJobResolution { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "horizontalDpi": horizontalDpi, "id": id, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart index 8a1efff62..082b1cea8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/print_job_state.g.dart @@ -192,20 +192,43 @@ class PrintJobState { return null; } + /// Gets a possible [PrintJobState] instance value with name [name]. + /// + /// Goes through [PrintJobState.values] looking for a value with + /// name [name], as reported by [PrintJobState.name]. + /// Returns the first value with the given name, otherwise `null`. + static PrintJobState? byName(String? name) { + if (name != null) { + try { + return PrintJobState.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PrintJobState] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in PrintJobState.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'BLOCKED'; @@ -224,4 +247,15 @@ class PrintJobState { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/printer.dart b/flutter_inappwebview_platform_interface/lib/src/types/printer.dart index f66c67b92..2216b49c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/printer.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/printer.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../print_job/main.dart'; +import 'enum_method.dart'; part 'printer.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart index ec54b75cf..6c95e6a25 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/printer.g.dart @@ -35,7 +35,7 @@ class Printer { Printer({this.id, this.languageLevel, this.name, this.type}); ///Gets a possible [Printer] instance from a [Map] value. - static Printer? fromMap(Map? map) { + static Printer? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -49,7 +49,7 @@ class Printer { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "id": id, "languageLevel": languageLevel, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart new file mode 100644 index 000000000..972807f91 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.dart @@ -0,0 +1,77 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'frame_info.dart'; +import 'process_failed_kind.dart'; +import 'process_failed_reason.dart'; +import 'enum_method.dart'; + +part 'process_failed_detail.g.dart'; + +///An object that contains information about a frame on a webpage. +@ExchangeableObject() +class ProcessFailedDetail_ { + ///The kind of process failure that has occurred. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + ProcessFailedKind_ kind; + + ///The exit code of the failing process, for telemetry purposes. + /// + ///The exit code is always STILL_ACTIVE (259) when [ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + int? exitCode; + + ///Description of the process assigned by the WebView2 Runtime. + /// + ///This is a technical English term appropriate for logging or development purposes, and not localized for the end user. + ///It applies to utility processes (for example, "Audio Service", "Video Capture") and plugin processes (for example, "Flash"). + ///The returned [processDescription] is empty if the WebView2 Runtime did not assign a description to the process. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? processDescription; + + ///The reason for the process failure. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + ProcessFailedReason_? reason; + + ///This property is the full path of the module that caused the crash in cases of Windows Code Integrity failures. + /// + ///Windows Code Integrity is a feature that verifies the integrity and authenticity of dynamic-link libraries (DLLs) on Windows systems. + ///It ensures that only trusted code can run on the system and prevents unauthorized or malicious modifications. + ///When ProcessFailed occurred due to a failed Code Integrity check, this property returns the full path of the file that was prevented from loading on the system. + ///The webview2 process which tried to load the DLL will fail with exit code STATUS_INVALID_IMAGE_HASH(-1073740760). + ///A file can fail integrity check for various reasons, such as: + ///- It has an invalid or missing signature that does not match the publisher or signer of the file. + ///- It has been tampered with or corrupted by malware or other software. + ///- It has been blocked by an administrator or a security policy. This property always will be the empty string if failure is not caused by STATUS_INVALID_IMAGE_HASH. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + String? failureSourceModulePath; + + ///The collection of [FrameInfo]s for frames in the WebView that were being rendered by the failed process. + /// + ///The content in these frames is replaced with an error page. + ///This is only available when [ProcessFailedKind] is [ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED]; + ///frames is null for all other process failure kinds, including the case in which the failed process was the renderer + ///for the main frame and subframes within it, for which the failure kind is [ProcessFailedKind.RENDER_PROCESS_EXITED]. + @SupportedPlatforms(platforms: [ + WindowsPlatform(), + ]) + List? frameInfos; + + ProcessFailedDetail_({ + required this.kind, + this.exitCode, + this.processDescription, + this.reason, + this.failureSourceModulePath, + this.frameInfos, + }); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart new file mode 100644 index 000000000..6b0aa7f32 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_detail.g.dart @@ -0,0 +1,135 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_detail.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///An object that contains information about a frame on a webpage. +class ProcessFailedDetail { + ///The exit code of the failing process, for telemetry purposes. + /// + ///The exit code is always STILL_ACTIVE (259) when [ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + int? exitCode; + + ///This property is the full path of the module that caused the crash in cases of Windows Code Integrity failures. + /// + ///Windows Code Integrity is a feature that verifies the integrity and authenticity of dynamic-link libraries (DLLs) on Windows systems. + ///It ensures that only trusted code can run on the system and prevents unauthorized or malicious modifications. + ///When ProcessFailed occurred due to a failed Code Integrity check, this property returns the full path of the file that was prevented from loading on the system. + ///The webview2 process which tried to load the DLL will fail with exit code STATUS_INVALID_IMAGE_HASH(-1073740760). + ///A file can fail integrity check for various reasons, such as: + ///- It has an invalid or missing signature that does not match the publisher or signer of the file. + ///- It has been tampered with or corrupted by malware or other software. + ///- It has been blocked by an administrator or a security policy. This property always will be the empty string if failure is not caused by STATUS_INVALID_IMAGE_HASH. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? failureSourceModulePath; + + ///The collection of [FrameInfo]s for frames in the WebView that were being rendered by the failed process. + /// + ///The content in these frames is replaced with an error page. + ///This is only available when [ProcessFailedKind] is [ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED]; + ///frames is null for all other process failure kinds, including the case in which the failed process was the renderer + ///for the main frame and subframes within it, for which the failure kind is [ProcessFailedKind.RENDER_PROCESS_EXITED]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + List? frameInfos; + + ///The kind of process failure that has occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ProcessFailedKind kind; + + ///Description of the process assigned by the WebView2 Runtime. + /// + ///This is a technical English term appropriate for logging or development purposes, and not localized for the end user. + ///It applies to utility processes (for example, "Audio Service", "Video Capture") and plugin processes (for example, "Flash"). + ///The returned [processDescription] is empty if the WebView2 Runtime did not assign a description to the process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + String? processDescription; + + ///The reason for the process failure. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ProcessFailedReason? reason; + ProcessFailedDetail( + {this.exitCode, + this.failureSourceModulePath, + this.frameInfos, + required this.kind, + this.processDescription, + this.reason}); + + ///Gets a possible [ProcessFailedDetail] instance from a [Map] value. + static ProcessFailedDetail? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ProcessFailedDetail( + exitCode: map['exitCode'], + failureSourceModulePath: map['failureSourceModulePath'], + frameInfos: map['frameInfos'] != null + ? List.from(map['frameInfos'].map((e) => FrameInfo.fromMap( + e?.cast(), + enumMethod: enumMethod)!)) + : null, + kind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProcessFailedKind.fromNativeValue(map['kind']), + EnumMethod.value => ProcessFailedKind.fromValue(map['kind']), + EnumMethod.name => ProcessFailedKind.byName(map['kind']) + }!, + processDescription: map['processDescription'], + reason: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProcessFailedReason.fromNativeValue(map['reason']), + EnumMethod.value => ProcessFailedReason.fromValue(map['reason']), + EnumMethod.name => ProcessFailedReason.byName(map['reason']) + }, + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "exitCode": exitCode, + "failureSourceModulePath": failureSourceModulePath, + "frameInfos": + frameInfos?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "kind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => kind.toNativeValue(), + EnumMethod.value => kind.toValue(), + EnumMethod.name => kind.name() + }, + "processDescription": processDescription, + "reason": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => reason?.toNativeValue(), + EnumMethod.value => reason?.toValue(), + EnumMethod.name => reason?.name() + }, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ProcessFailedDetail{exitCode: $exitCode, failureSourceModulePath: $failureSourceModulePath, frameInfos: $frameInfos, kind: $kind, processDescription: $processDescription, reason: $reason}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart new file mode 100644 index 000000000..cee266e32 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.dart @@ -0,0 +1,152 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'process_failed_kind.g.dart'; + +///Class used to indicate the kind of process failure that has occurred. +@ExchangeableEnum() +class ProcessFailedKind_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const ProcessFailedKind_._internal(this._value); + + ///Indicates that the browser process ended unexpectedly. The WebView automatically moves to the Closed state. + ///The app has to recreate a new WebView to recover from this failure. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 0), + ]) + static const BROWSER_PROCESS_EXITED = + const ProcessFailedKind_._internal('BROWSER_PROCESS_EXITED'); + + ///Indicates that the main frame's render process ended unexpectedly. Any subframes in the WebView will be gone too. + ///A new render process is created automatically and navigated to an error page. + ///You can use the reload method to try to recover from this failure. Alternatively, you can close and recreate the WebView. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 1), + ]) + static const RENDER_PROCESS_EXITED = + const ProcessFailedKind_._internal('RENDER_PROCESS_EXITED'); + + ///Indicates that the main frame's render process is unresponsive. + ///Renderer process unresponsiveness can happen for the following reasons: + /// + ///* There is a **long-running script** being executed. For example, the + ///web content in your WebView might be performing a synchronous XHR, or have + ///entered an infinite loop. + ///* The **system is busy**. + /// + ///The process failed event will continue to be raised every few seconds + ///until the renderer process has become responsive again. The application + ///can consider taking action if the event keeps being raised. For example, + ///the application might show UI for the user to decide to keep waiting or + ///reload the page, or navigate away. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 2), + ]) + static const RENDER_PROCESS_UNRESPONSIVE = + const ProcessFailedKind_._internal('RENDER_PROCESS_UNRESPONSIVE'); + + ///Indicates that a frame-only render process ended unexpectedly. + ///The process exit does not affect the top-level document, only a subset of the subframes within it. + ///The content in these frames is replaced with an error page in the frame. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 3), + ]) + static const FRAME_RENDER_PROCESS_EXITED = + const ProcessFailedKind_._internal('FRAME_RENDER_PROCESS_EXITED'); + + ///Indicates that a utility process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 4), + ]) + static const UTILITY_PROCESS_EXITED = + const ProcessFailedKind_._internal('UTILITY_PROCESS_EXITED'); + + ///Indicates that a sandbox helper process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 5), + ]) + static const SANDBOX_HELPER_PROCESS_EXITED = + const ProcessFailedKind_._internal('SANDBOX_HELPER_PROCESS_EXITED'); + + ///Indicates that the GPU process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 6), + ]) + static const GPU_PROCESS_EXITED = + const ProcessFailedKind_._internal('GPU_PROCESS_EXITED'); + + ///Indicates that a PPAPI plugin process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 7), + ]) + static const PPAPI_PLUGIN_PROCESS_EXITED = + const ProcessFailedKind_._internal('PPAPI_PLUGIN_PROCESS_EXITED'); + + ///Indicates that a PPAPI plugin broker process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 8), + ]) + static const PPAPI_BROKER_PROCESS_EXITED = + const ProcessFailedKind_._internal('PPAPI_BROKER_PROCESS_EXITED'); + + ///Indicates that a process of unspecified kind ended unexpectedly. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind', + value: 9), + ]) + static const UNKNOWN_PROCESS_EXITED = + const ProcessFailedKind_._internal('UNKNOWN_PROCESS_EXITED'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart new file mode 100644 index 000000000..729477570 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_kind.g.dart @@ -0,0 +1,314 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_kind.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the kind of process failure that has occurred. +class ProcessFailedKind { + final String _value; + final dynamic _nativeValue; + const ProcessFailedKind._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ProcessFailedKind._internalMultiPlatform( + String value, Function nativeValue) => + ProcessFailedKind._internal(value, nativeValue()); + + ///Indicates that the browser process ended unexpectedly. The WebView automatically moves to the Closed state. + ///The app has to recreate a new WebView to recover from this failure. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final BROWSER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('BROWSER_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///Indicates that a frame-only render process ended unexpectedly. + ///The process exit does not affect the top-level document, only a subset of the subframes within it. + ///The content in these frames is replaced with an error page in the frame. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final FRAME_RENDER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('FRAME_RENDER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///Indicates that the GPU process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final GPU_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('GPU_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 6; + default: + break; + } + return null; + }); + + ///Indicates that a PPAPI plugin broker process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final PPAPI_BROKER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('PPAPI_BROKER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 8; + default: + break; + } + return null; + }); + + ///Indicates that a PPAPI plugin process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final PPAPI_PLUGIN_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('PPAPI_PLUGIN_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 7; + default: + break; + } + return null; + }); + + ///Indicates that the main frame's render process ended unexpectedly. Any subframes in the WebView will be gone too. + ///A new render process is created automatically and navigated to an error page. + ///You can use the reload method to try to recover from this failure. Alternatively, you can close and recreate the WebView. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final RENDER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('RENDER_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Indicates that the main frame's render process is unresponsive. + ///Renderer process unresponsiveness can happen for the following reasons: + /// + ///* There is a **long-running script** being executed. For example, the + ///web content in your WebView might be performing a synchronous XHR, or have + ///entered an infinite loop. + ///* The **system is busy**. + /// + ///The process failed event will continue to be raised every few seconds + ///until the renderer process has become responsive again. The application + ///can consider taking action if the event keeps being raised. For example, + ///the application might show UI for the user to decide to keep waiting or + ///reload the page, or navigate away. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final RENDER_PROCESS_UNRESPONSIVE = + ProcessFailedKind._internalMultiPlatform('RENDER_PROCESS_UNRESPONSIVE', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///Indicates that a sandbox helper process ended unexpectedly. + ///This failure is not fatal. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final SANDBOX_HELPER_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('SANDBOX_HELPER_PROCESS_EXITED', + () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 5; + default: + break; + } + return null; + }); + + ///Indicates that a process of unspecified kind ended unexpectedly. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final UNKNOWN_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('UNKNOWN_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 9; + default: + break; + } + return null; + }); + + ///Indicates that a utility process ended unexpectedly. + ///The failed process is recreated automatically. + ///Your application does not need to handle recovery for this event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_kind)) + static final UTILITY_PROCESS_EXITED = + ProcessFailedKind._internalMultiPlatform('UTILITY_PROCESS_EXITED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///Set of all values of [ProcessFailedKind]. + static final Set values = [ + ProcessFailedKind.BROWSER_PROCESS_EXITED, + ProcessFailedKind.FRAME_RENDER_PROCESS_EXITED, + ProcessFailedKind.GPU_PROCESS_EXITED, + ProcessFailedKind.PPAPI_BROKER_PROCESS_EXITED, + ProcessFailedKind.PPAPI_PLUGIN_PROCESS_EXITED, + ProcessFailedKind.RENDER_PROCESS_EXITED, + ProcessFailedKind.RENDER_PROCESS_UNRESPONSIVE, + ProcessFailedKind.SANDBOX_HELPER_PROCESS_EXITED, + ProcessFailedKind.UNKNOWN_PROCESS_EXITED, + ProcessFailedKind.UTILITY_PROCESS_EXITED, + ].toSet(); + + ///Gets a possible [ProcessFailedKind] instance from [String] value. + static ProcessFailedKind? fromValue(String? value) { + if (value != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ProcessFailedKind] instance from a native value. + static ProcessFailedKind? fromNativeValue(dynamic value) { + if (value != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ProcessFailedKind] instance value with name [name]. + /// + /// Goes through [ProcessFailedKind.values] looking for a value with + /// name [name], as reported by [ProcessFailedKind.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProcessFailedKind? byName(String? name) { + if (name != null) { + try { + return ProcessFailedKind.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProcessFailedKind] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProcessFailedKind.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'BROWSER_PROCESS_EXITED': + return 'BROWSER_PROCESS_EXITED'; + case 'FRAME_RENDER_PROCESS_EXITED': + return 'FRAME_RENDER_PROCESS_EXITED'; + case 'GPU_PROCESS_EXITED': + return 'GPU_PROCESS_EXITED'; + case 'PPAPI_BROKER_PROCESS_EXITED': + return 'PPAPI_BROKER_PROCESS_EXITED'; + case 'PPAPI_PLUGIN_PROCESS_EXITED': + return 'PPAPI_PLUGIN_PROCESS_EXITED'; + case 'RENDER_PROCESS_EXITED': + return 'RENDER_PROCESS_EXITED'; + case 'RENDER_PROCESS_UNRESPONSIVE': + return 'RENDER_PROCESS_UNRESPONSIVE'; + case 'SANDBOX_HELPER_PROCESS_EXITED': + return 'SANDBOX_HELPER_PROCESS_EXITED'; + case 'UNKNOWN_PROCESS_EXITED': + return 'UNKNOWN_PROCESS_EXITED'; + case 'UTILITY_PROCESS_EXITED': + return 'UTILITY_PROCESS_EXITED'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart new file mode 100644 index 000000000..647e6e887 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.dart @@ -0,0 +1,77 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'process_failed_reason.g.dart'; + +///Class used to indicate the kind of process failure that has occurred. +@ExchangeableEnum() +class ProcessFailedReason_ { + // ignore: unused_field + final String _value; + // ignore: unused_field + final dynamic _nativeValue = null; + const ProcessFailedReason_._internal(this._value); + + ///An unexpected process failure occurred. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 0), + ]) + static const UNEXPECTED = const ProcessFailedReason_._internal('UNEXPECTED'); + + ///The process became unresponsive. This only applies to the main frame's render process. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_UNRESPONSIVE', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 1), + ]) + static const UNRESPONSIVE = + const ProcessFailedReason_._internal('UNRESPONSIVE'); + + ///The process was terminated. For example, from Task Manager. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_TERMINATED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 2), + ]) + static const TERMINATED = const ProcessFailedReason_._internal('TERMINATED'); + + ///The process crashed. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 3), + ]) + static const CRASHED = const ProcessFailedReason_._internal('CRASHED'); + + ///The process failed to launch. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_LAUNCH_FAILED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 4), + ]) + static const LAUNCH_FAILED = + const ProcessFailedReason_._internal('LAUNCH_FAILED'); + + ///The process terminated due to running out of memory. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_PROCESS_FAILED_REASON_OUT_OF_MEMORY', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason', + value: 5), + ]) + static const OUT_OF_MEMORY = + const ProcessFailedReason_._internal('OUT_OF_MEMORY'); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart new file mode 100644 index 000000000..648cc1447 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/process_failed_reason.g.dart @@ -0,0 +1,210 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'process_failed_reason.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class used to indicate the kind of process failure that has occurred. +class ProcessFailedReason { + final String _value; + final dynamic _nativeValue; + const ProcessFailedReason._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ProcessFailedReason._internalMultiPlatform( + String value, Function nativeValue) => + ProcessFailedReason._internal(value, nativeValue()); + + ///The process crashed. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final CRASHED = + ProcessFailedReason._internalMultiPlatform('CRASHED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 3; + default: + break; + } + return null; + }); + + ///The process failed to launch. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_LAUNCH_FAILED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final LAUNCH_FAILED = + ProcessFailedReason._internalMultiPlatform('LAUNCH_FAILED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + + ///The process terminated due to running out of memory. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_OUT_OF_MEMORY](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final OUT_OF_MEMORY = + ProcessFailedReason._internalMultiPlatform('OUT_OF_MEMORY', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 5; + default: + break; + } + return null; + }); + + ///The process was terminated. For example, from Task Manager. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_TERMINATED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final TERMINATED = + ProcessFailedReason._internalMultiPlatform('TERMINATED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 2; + default: + break; + } + return null; + }); + + ///An unexpected process failure occurred. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final UNEXPECTED = + ProcessFailedReason._internalMultiPlatform('UNEXPECTED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 0; + default: + break; + } + return null; + }); + + ///The process became unresponsive. This only applies to the main frame's render process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_PROCESS_FAILED_REASON_UNRESPONSIVE](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_process_failed_reason)) + static final UNRESPONSIVE = + ProcessFailedReason._internalMultiPlatform('UNRESPONSIVE', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + + ///Set of all values of [ProcessFailedReason]. + static final Set values = [ + ProcessFailedReason.CRASHED, + ProcessFailedReason.LAUNCH_FAILED, + ProcessFailedReason.OUT_OF_MEMORY, + ProcessFailedReason.TERMINATED, + ProcessFailedReason.UNEXPECTED, + ProcessFailedReason.UNRESPONSIVE, + ].toSet(); + + ///Gets a possible [ProcessFailedReason] instance from [String] value. + static ProcessFailedReason? fromValue(String? value) { + if (value != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ProcessFailedReason] instance from a native value. + static ProcessFailedReason? fromNativeValue(dynamic value) { + if (value != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ProcessFailedReason] instance value with name [name]. + /// + /// Goes through [ProcessFailedReason.values] looking for a value with + /// name [name], as reported by [ProcessFailedReason.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProcessFailedReason? byName(String? name) { + if (name != null) { + try { + return ProcessFailedReason.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProcessFailedReason] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProcessFailedReason.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [dynamic] native value. + dynamic toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CRASHED': + return 'CRASHED'; + case 'LAUNCH_FAILED': + return 'LAUNCH_FAILED'; + case 'OUT_OF_MEMORY': + return 'OUT_OF_MEMORY'; + case 'TERMINATED': + return 'TERMINATED'; + case 'UNEXPECTED': + return 'UNEXPECTED'; + case 'UNRESPONSIVE': + return 'UNRESPONSIVE'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart new file mode 100644 index 000000000..52058f542 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.dart @@ -0,0 +1,46 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import 'enum_method.dart'; + +part 'proxy_relay_hop.g.dart'; + +///Relay servers are secure HTTP proxies that allow proxying TCP traffic using the +///CONNECT method and UDP traffic using the connect-udp protocol defined in [RFC 9298](https://www.rfc-editor.org/rfc/rfc9298.html). +/// +///If [http3RelayEndpoint] is not null, it creates a configuration for a secure relay accessible using HTTP/3, with an optional HTTP/2 fallback using [http2RelayEndpoint]. +///Otherwise, if [http2RelayEndpoint] is not null, it sreates a configuration for a secure relay accessible only using HTTP/2. +/// +///At least one of [http3RelayEndpoint] or [http2RelayEndpoint] must be non-null. +@ExchangeableObject() +class ProxyRelayHop_ { + ///A URL or host endpoint identifying the relay server accessible using HTTP/3. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? http3RelayEndpoint; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/2. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? http2RelayEndpoint; + + ///A dictionary of additional HTTP headers to send as part of CONNECT requests to the relay. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + Map? additionalHTTPHeaders; + + @ExchangeableObjectConstructor() + ProxyRelayHop_({ + this.http3RelayEndpoint, + this.http2RelayEndpoint, + this.additionalHTTPHeaders, + }) { + assert(http3RelayEndpoint != null || http2RelayEndpoint != null, + "At least one of http3RelayEndpoint or http2RelayEndpoint must be non-null"); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart new file mode 100644 index 000000000..f7ffce3b2 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_relay_hop.g.dart @@ -0,0 +1,78 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'proxy_relay_hop.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Relay servers are secure HTTP proxies that allow proxying TCP traffic using the +///CONNECT method and UDP traffic using the connect-udp protocol defined in [RFC 9298](https://www.rfc-editor.org/rfc/rfc9298.html). +/// +///If [http3RelayEndpoint] is not null, it creates a configuration for a secure relay accessible using HTTP/3, with an optional HTTP/2 fallback using [http2RelayEndpoint]. +///Otherwise, if [http2RelayEndpoint] is not null, it sreates a configuration for a secure relay accessible only using HTTP/2. +/// +///At least one of [http3RelayEndpoint] or [http2RelayEndpoint] must be non-null. +class ProxyRelayHop { + ///A dictionary of additional HTTP headers to send as part of CONNECT requests to the relay. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + Map? additionalHTTPHeaders; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/2. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? http2RelayEndpoint; + + ///A URL or host endpoint identifying the relay server accessible using HTTP/3. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? http3RelayEndpoint; + ProxyRelayHop( + {this.http3RelayEndpoint, + this.http2RelayEndpoint, + this.additionalHTTPHeaders}) { + assert(http3RelayEndpoint != null || http2RelayEndpoint != null, + "At least one of http3RelayEndpoint or http2RelayEndpoint must be non-null"); + } + + ///Gets a possible [ProxyRelayHop] instance from a [Map] value. + static ProxyRelayHop? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ProxyRelayHop( + additionalHTTPHeaders: + map['additionalHTTPHeaders']?.cast(), + http2RelayEndpoint: map['http2RelayEndpoint'], + http3RelayEndpoint: map['http3RelayEndpoint'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "additionalHTTPHeaders": additionalHTTPHeaders, + "http2RelayEndpoint": http2RelayEndpoint, + "http3RelayEndpoint": http3RelayEndpoint, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ProxyRelayHop{additionalHTTPHeaders: $additionalHTTPHeaders, http2RelayEndpoint: $http2RelayEndpoint, http3RelayEndpoint: $http3RelayEndpoint}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart index 031be516d..2a343f0c9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'proxy_scheme_filter.dart'; part 'proxy_rule.g.dart'; @@ -8,10 +9,64 @@ part 'proxy_rule.g.dart'; @ExchangeableObject() class ProxyRule_ { ///Represents the scheme filter. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + ]) ProxySchemeFilter_? schemeFilter; ///Represents the proxy URL. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + ]) String url; - ProxyRule_({required this.url, this.schemeFilter}); + ///A Boolean that indicates whether or not a proxy configuration allows failover to non-proxied connections. + ///Failover isn’t allowed by default. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + bool? allowFailover; + + ///Sets a username to use as authentication for a proxy configuration. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? username; + + ///Sets a password to use as authentication for a proxy configuration. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + String? password; + + ///Define an array of domains to determine which hosts should not use the proxy. + ///If the array is empty, no domains are excluded. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + List? excludedDomains; + + ///Define an array of domains to determine which hosts should use the proxy. If the array is empty, + ///all domains are allowed to use the proxy other than domains listed in [excludedDomains]. + @SupportedPlatforms(platforms: [ + IOSPlatform(), + MacOSPlatform(), + ]) + List? matchDomains; + + ProxyRule_({ + required this.url, + this.schemeFilter, + this.allowFailover, + this.username, + this.password, + this.excludedDomains, + this.matchDomains, + }); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart index f81094c5c..02115af81 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_rule.g.dart @@ -8,30 +8,107 @@ part of 'proxy_rule.dart'; ///Class that holds a scheme filter and a proxy URL. class ProxyRule { + ///A Boolean that indicates whether or not a proxy configuration allows failover to non-proxied connections. + ///Failover isn’t allowed by default. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + bool? allowFailover; + + ///Define an array of domains to determine which hosts should not use the proxy. + ///If the array is empty, no domains are excluded. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + List? excludedDomains; + + ///Define an array of domains to determine which hosts should use the proxy. If the array is empty, + ///all domains are allowed to use the proxy other than domains listed in [excludedDomains]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + List? matchDomains; + + ///Sets a password to use as authentication for a proxy configuration. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? password; + ///Represents the scheme filter. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ProxySchemeFilter? schemeFilter; ///Represents the proxy URL. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS String url; - ProxyRule({this.schemeFilter, required this.url}); + + ///Sets a username to use as authentication for a proxy configuration. + /// + ///**Officially Supported Platforms/Implementations**: + ///- iOS + ///- MacOS + String? username; + ProxyRule( + {this.allowFailover, + this.excludedDomains, + this.matchDomains, + this.password, + this.schemeFilter, + required this.url, + this.username}); ///Gets a possible [ProxyRule] instance from a [Map] value. - static ProxyRule? fromMap(Map? map) { + static ProxyRule? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ProxyRule( - schemeFilter: ProxySchemeFilter.fromNativeValue(map['schemeFilter']), + allowFailover: map['allowFailover'], + excludedDomains: map['excludedDomains'] != null + ? List.from(map['excludedDomains']!.cast()) + : null, + matchDomains: map['matchDomains'] != null + ? List.from(map['matchDomains']!.cast()) + : null, + password: map['password'], + schemeFilter: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ProxySchemeFilter.fromNativeValue(map['schemeFilter']), + EnumMethod.value => ProxySchemeFilter.fromValue(map['schemeFilter']), + EnumMethod.name => ProxySchemeFilter.byName(map['schemeFilter']) + }, url: map['url'], + username: map['username'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "schemeFilter": schemeFilter?.toNativeValue(), + "allowFailover": allowFailover, + "excludedDomains": excludedDomains, + "matchDomains": matchDomains, + "password": password, + "schemeFilter": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => schemeFilter?.toNativeValue(), + EnumMethod.value => schemeFilter?.toValue(), + EnumMethod.name => schemeFilter?.name() + }, "url": url, + "username": username, }; } @@ -42,6 +119,6 @@ class ProxyRule { @override String toString() { - return 'ProxyRule{schemeFilter: $schemeFilter, url: $url}'; + return 'ProxyRule{allowFailover: $allowFailover, excludedDomains: $excludedDomains, matchDomains: $matchDomains, password: $password, schemeFilter: $schemeFilter, url: $url, username: $username}'; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart index 7abc64a01..56b9414e3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/proxy_scheme_filter.g.dart @@ -58,12 +58,55 @@ class ProxySchemeFilter { return null; } + /// Gets a possible [ProxySchemeFilter] instance value with name [name]. + /// + /// Goes through [ProxySchemeFilter.values] looking for a value with + /// name [name], as reported by [ProxySchemeFilter.name]. + /// Returns the first value with the given name, otherwise `null`. + static ProxySchemeFilter? byName(String? name) { + if (name != null) { + try { + return ProxySchemeFilter.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ProxySchemeFilter] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ProxySchemeFilter.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case '*': + return 'MATCH_ALL_SCHEMES'; + case 'http': + return 'MATCH_HTTP'; + case 'https': + return 'MATCH_HTTPS'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart index bb53e8757..4a518d8df 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/pull_to_refresh_size.g.dart @@ -54,12 +54,53 @@ class PullToRefreshSize { return null; } + /// Gets a possible [PullToRefreshSize] instance value with name [name]. + /// + /// Goes through [PullToRefreshSize.values] looking for a value with + /// name [name], as reported by [PullToRefreshSize.name]. + /// Returns the first value with the given name, otherwise `null`. + static PullToRefreshSize? byName(String? name) { + if (name != null) { + try { + return PullToRefreshSize.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [PullToRefreshSize] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in PullToRefreshSize.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'DEFAULT'; + case 0: + return 'LARGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,13 +109,7 @@ class PullToRefreshSize { @override String toString() { - switch (_value) { - case 1: - return 'DEFAULT'; - case 0: - return 'LARGE'; - } - return _value.toString(); + return name(); } } @@ -128,12 +163,53 @@ class AndroidPullToRefreshSize { return null; } + /// Gets a possible [AndroidPullToRefreshSize] instance value with name [name]. + /// + /// Goes through [AndroidPullToRefreshSize.values] looking for a value with + /// name [name], as reported by [AndroidPullToRefreshSize.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidPullToRefreshSize? byName(String? name) { + if (name != null) { + try { + return AndroidPullToRefreshSize.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidPullToRefreshSize] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidPullToRefreshSize.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'DEFAULT'; + case 0: + return 'LARGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -142,12 +218,6 @@ class AndroidPullToRefreshSize { @override String toString() { - switch (_value) { - case 1: - return 'DEFAULT'; - case 0: - return 'LARGE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart index db31b4b3d..6ea14e309 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/referrer_policy.g.dart @@ -91,12 +91,64 @@ class ReferrerPolicy { return null; } + /// Gets a possible [ReferrerPolicy] instance value with name [name]. + /// + /// Goes through [ReferrerPolicy.values] looking for a value with + /// name [name], as reported by [ReferrerPolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static ReferrerPolicy? byName(String? name) { + if (name != null) { + try { + return ReferrerPolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ReferrerPolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in ReferrerPolicy.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'no-referrer': + return 'NO_REFERRER'; + case 'no-referrer-when-downgrade': + return 'NO_REFERRER_WHEN_DOWNGRADE'; + case 'origin': + return 'ORIGIN'; + case 'origin-when-cross-origin': + return 'ORIGIN_WHEN_CROSS_ORIGIN'; + case 'same-origin': + return 'SAME_ORIGIN'; + case 'strict-origin': + return 'STRICT_ORIGIN'; + case 'strict-origin-when-cross-origin': + return 'STRICT_ORIGIN_WHEN_CROSS_ORIGIN'; + case 'unsafe-url': + return 'UNSAFE_URL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart index 043a3cc82..c78c675d8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'renderer_priority.dart'; +import 'enum_method.dart'; part 'render_process_gone_detail.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart index 39f2c3421..ae18a1e7f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/render_process_gone_detail.g.dart @@ -21,23 +21,34 @@ class RenderProcessGoneDetail { {required this.didCrash, this.rendererPriorityAtExit}); ///Gets a possible [RenderProcessGoneDetail] instance from a [Map] value. - static RenderProcessGoneDetail? fromMap(Map? map) { + static RenderProcessGoneDetail? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = RenderProcessGoneDetail( didCrash: map['didCrash'], - rendererPriorityAtExit: + rendererPriorityAtExit: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => RendererPriority.fromNativeValue(map['rendererPriorityAtExit']), + EnumMethod.value => + RendererPriority.fromValue(map['rendererPriorityAtExit']), + EnumMethod.name => + RendererPriority.byName(map['rendererPriorityAtExit']) + }, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "didCrash": didCrash, - "rendererPriorityAtExit": rendererPriorityAtExit?.toNativeValue(), + "rendererPriorityAtExit": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => rendererPriorityAtExit?.toNativeValue(), + EnumMethod.value => rendererPriorityAtExit?.toValue(), + EnumMethod.name => rendererPriorityAtExit?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart index 2b2b7567a..e8116339a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority.g.dart @@ -59,20 +59,44 @@ class RendererPriority { return null; } + /// Gets a possible [RendererPriority] instance value with name [name]. + /// + /// Goes through [RendererPriority.values] looking for a value with + /// name [name], as reported by [RendererPriority.name]. + /// Returns the first value with the given name, otherwise `null`. + static RendererPriority? byName(String? name) { + if (name != null) { + try { + return RendererPriority.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [RendererPriority] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in RendererPriority.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'RENDERER_PRIORITY_BOUND'; @@ -83,4 +107,15 @@ class RendererPriority { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart index 8c599e964..5bb4fc794 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'renderer_priority.dart'; +import 'enum_method.dart'; part 'renderer_priority_policy.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart index 1ef810ea8..9e42b49a1 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/renderer_priority_policy.g.dart @@ -22,22 +22,34 @@ class RendererPriorityPolicy { {this.rendererRequestedPriority, required this.waivedWhenNotVisible}); ///Gets a possible [RendererPriorityPolicy] instance from a [Map] value. - static RendererPriorityPolicy? fromMap(Map? map) { + static RendererPriorityPolicy? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = RendererPriorityPolicy( - rendererRequestedPriority: + rendererRequestedPriority: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => RendererPriority.fromNativeValue(map['rendererRequestedPriority']), + EnumMethod.value => + RendererPriority.fromValue(map['rendererRequestedPriority']), + EnumMethod.name => + RendererPriority.byName(map['rendererRequestedPriority']) + }, waivedWhenNotVisible: map['waivedWhenNotVisible'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "rendererRequestedPriority": rendererRequestedPriority?.toNativeValue(), + "rendererRequestedPriority": switch ( + enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => rendererRequestedPriority?.toNativeValue(), + EnumMethod.value => rendererRequestedPriority?.toValue(), + EnumMethod.name => rendererRequestedPriority?.name() + }, "waivedWhenNotVisible": waivedWhenNotVisible, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart index 77f56c001..14d9d840c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'request_focus_node_href_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart index 57c859404..eeea135fb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_focus_node_href_result.g.dart @@ -19,7 +19,8 @@ class RequestFocusNodeHrefResult { RequestFocusNodeHrefResult({this.src, this.title, this.url}); ///Gets a possible [RequestFocusNodeHrefResult] instance from a [Map] value. - static RequestFocusNodeHrefResult? fromMap(Map? map) { + static RequestFocusNodeHrefResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -32,7 +33,7 @@ class RequestFocusNodeHrefResult { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "src": src, "title": title, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart index 0c5c622f3..c9f4eefc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'request_image_ref_result.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart index bb42cf518..758a7e002 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/request_image_ref_result.g.dart @@ -13,7 +13,8 @@ class RequestImageRefResult { RequestImageRefResult({this.url}); ///Gets a possible [RequestImageRefResult] instance from a [Map] value. - static RequestImageRefResult? fromMap(Map? map) { + static RequestImageRefResult? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -24,7 +25,7 @@ class RequestImageRefResult { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "url": url?.toString(), }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart index 35935cbba..940b52434 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import '../in_app_webview/platform_webview.dart'; import 'safe_browsing_response_action.dart'; +import 'enum_method.dart'; part 'safe_browsing_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart index 4c24640e5..ffd1ae3a6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response.g.dart @@ -19,20 +19,32 @@ class SafeBrowsingResponse { this.report = true}); ///Gets a possible [SafeBrowsingResponse] instance from a [Map] value. - static SafeBrowsingResponse? fromMap(Map? map) { + static SafeBrowsingResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = SafeBrowsingResponse(); - instance.action = SafeBrowsingResponseAction.fromNativeValue(map['action']); - instance.report = map['report']; + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + SafeBrowsingResponseAction.fromNativeValue(map['action']), + EnumMethod.value => SafeBrowsingResponseAction.fromValue(map['action']), + EnumMethod.name => SafeBrowsingResponseAction.byName(map['action']) + }; + if (map['report'] != null) { + instance.report = map['report']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, "report": report, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart index c435bb404..5a1645263 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_response_action.g.dart @@ -58,20 +58,45 @@ class SafeBrowsingResponseAction { return null; } + /// Gets a possible [SafeBrowsingResponseAction] instance value with name [name]. + /// + /// Goes through [SafeBrowsingResponseAction.values] looking for a value with + /// name [name], as reported by [SafeBrowsingResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static SafeBrowsingResponseAction? byName(String? name) { + if (name != null) { + try { + return SafeBrowsingResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SafeBrowsingResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SafeBrowsingResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'BACK_TO_SAFETY'; @@ -82,4 +107,15 @@ class SafeBrowsingResponseAction { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart index 763455f1e..f91cc5957 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/safe_browsing_threat.g.dart @@ -74,20 +74,44 @@ class SafeBrowsingThreat { return null; } + /// Gets a possible [SafeBrowsingThreat] instance value with name [name]. + /// + /// Goes through [SafeBrowsingThreat.values] looking for a value with + /// name [name], as reported by [SafeBrowsingThreat.name]. + /// Returns the first value with the given name, otherwise `null`. + static SafeBrowsingThreat? byName(String? name) { + if (name != null) { + try { + return SafeBrowsingThreat.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SafeBrowsingThreat] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SafeBrowsingThreat.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'SAFE_BROWSING_THREAT_BILLING'; @@ -102,4 +126,15 @@ class SafeBrowsingThreat { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart index ca22db314..e946bc67d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/sandbox.g.dart @@ -88,6 +88,34 @@ class Sandbox { Sandbox.ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION, ].toSet(); + /// Gets a possible [Sandbox] instance value with name [name]. + /// + /// Goes through [Sandbox.values] looking for a value with + /// name [name], as reported by [Sandbox.name]. + /// Returns the first value with the given name, otherwise `null`. + static Sandbox? byName(String? name) { + if (name != null) { + try { + return Sandbox.values.firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [Sandbox] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in Sandbox.values) value.name(): value + }; + ///Gets a possible [Sandbox] instance from a native value. static Sandbox? fromNativeValue(String? value) { if (value == null) { @@ -129,6 +157,41 @@ class Sandbox { ///Gets [String?] native value. String? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALLOW_ALL'; + case 'allow-downloads': + return 'ALLOW_DOWNLOADS'; + case 'allow-forms': + return 'ALLOW_FORMS'; + case 'allow-modals': + return 'ALLOW_MODALS'; + case 'null': + return 'ALLOW_NONE'; + case 'allow-orientation-lock': + return 'ALLOW_ORIENTATION_LOCK'; + case 'allow-pointer-lock': + return 'ALLOW_POINTER_LOCK'; + case 'allow-popups': + return 'ALLOW_POPUPS'; + case 'allow-popups-to-escape-sandbox': + return 'ALLOW_POPUPS_TO_ESCAPE_SANDBOX'; + case 'allow-presentation': + return 'ALLOW_PRESENTATION'; + case 'allow-same-origin': + return 'ALLOW_SAME_ORIGIN'; + case 'allow-scripts': + return 'ALLOW_SCRIPTS'; + case 'allow-top-navigation': + return 'ALLOW_TOP_NAVIGATION'; + case 'allow-top-navigation-by-user-activation': + return 'ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart index f08c5a81f..fe6b18137 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'in_app_webview_rect.dart'; import 'compress_format.dart'; +import 'enum_method.dart'; part 'screenshot_configuration.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart index 460c7890d..b25b22637 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/screenshot_configuration.g.dart @@ -77,29 +77,45 @@ class ScreenshotConfiguration { } ///Gets a possible [ScreenshotConfiguration] instance from a [Map] value. - static ScreenshotConfiguration? fromMap(Map? map) { + static ScreenshotConfiguration? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ScreenshotConfiguration( iosAfterScreenUpdates: map['afterScreenUpdates'], - rect: InAppWebViewRect.fromMap(map['rect']?.cast()), + rect: InAppWebViewRect.fromMap(map['rect']?.cast(), + enumMethod: enumMethod), snapshotWidth: map['snapshotWidth'], ); - instance.afterScreenUpdates = map['afterScreenUpdates']; - instance.compressFormat = - CompressFormat.fromNativeValue(map['compressFormat'])!; - instance.quality = map['quality']; + if (map['afterScreenUpdates'] != null) { + instance.afterScreenUpdates = map['afterScreenUpdates']; + } + if (map['compressFormat'] != null) { + instance.compressFormat = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CompressFormat.fromNativeValue(map['compressFormat']), + EnumMethod.value => CompressFormat.fromValue(map['compressFormat']), + EnumMethod.name => CompressFormat.byName(map['compressFormat']) + }!; + } + if (map['quality'] != null) { + instance.quality = map['quality']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "afterScreenUpdates": afterScreenUpdates, - "compressFormat": compressFormat.toNativeValue(), + "compressFormat": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => compressFormat.toNativeValue(), + EnumMethod.value => compressFormat.toValue(), + EnumMethod.name => compressFormat.name() + }, "quality": quality, - "rect": rect?.toMap(), + "rect": rect?.toMap(enumMethod: enumMethod), "snapshotWidth": snapshotWidth, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart index 05a40266f..583ef1283 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/platform_inappwebview_controller.dart'; import 'cross_origin.dart'; import 'referrer_policy.dart'; +import 'enum_method.dart'; part 'script_html_tag_attributes.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart index 4ca725079..0d13f3fc7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/script_html_tag_attributes.g.dart @@ -86,35 +86,56 @@ class ScriptHtmlTagAttributes { } ///Gets a possible [ScriptHtmlTagAttributes] instance from a [Map] value. - static ScriptHtmlTagAttributes? fromMap(Map? map) { + static ScriptHtmlTagAttributes? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ScriptHtmlTagAttributes( async: map['async'], - crossOrigin: CrossOrigin.fromNativeValue(map['crossOrigin']), + crossOrigin: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + CrossOrigin.fromNativeValue(map['crossOrigin']), + EnumMethod.value => CrossOrigin.fromValue(map['crossOrigin']), + EnumMethod.name => CrossOrigin.byName(map['crossOrigin']) + }, defer: map['defer'], id: map['id'], integrity: map['integrity'], noModule: map['noModule'], nonce: map['nonce'], - referrerPolicy: ReferrerPolicy.fromNativeValue(map['referrerPolicy']), + referrerPolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ReferrerPolicy.fromNativeValue(map['referrerPolicy']), + EnumMethod.value => ReferrerPolicy.fromValue(map['referrerPolicy']), + EnumMethod.name => ReferrerPolicy.byName(map['referrerPolicy']) + }, ); - instance.type = map['type']; + if (map['type'] != null) { + instance.type = map['type']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "async": async, - "crossOrigin": crossOrigin?.toNativeValue(), + "crossOrigin": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => crossOrigin?.toNativeValue(), + EnumMethod.value => crossOrigin?.toValue(), + EnumMethod.name => crossOrigin?.name() + }, "defer": defer, "id": id, "integrity": integrity, "noModule": noModule, "nonce": nonce, - "referrerPolicy": referrerPolicy?.toNativeValue(), + "referrerPolicy": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => referrerPolicy?.toNativeValue(), + EnumMethod.value => referrerPolicy?.toValue(), + EnumMethod.name => referrerPolicy?.name() + }, "type": type, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart index 871f46754..7acdeb53d 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollbar_style.g.dart @@ -75,20 +75,43 @@ class ScrollBarStyle { return null; } + /// Gets a possible [ScrollBarStyle] instance value with name [name]. + /// + /// Goes through [ScrollBarStyle.values] looking for a value with + /// name [name], as reported by [ScrollBarStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollBarStyle? byName(String? name) { + if (name != null) { + try { + return ScrollBarStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollBarStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in ScrollBarStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 16777216: return 'SCROLLBARS_INSIDE_INSET'; @@ -101,6 +124,17 @@ class ScrollBarStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the style of the scrollbars. @@ -176,20 +210,44 @@ class AndroidScrollBarStyle { return null; } + /// Gets a possible [AndroidScrollBarStyle] instance value with name [name]. + /// + /// Goes through [AndroidScrollBarStyle.values] looking for a value with + /// name [name], as reported by [AndroidScrollBarStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidScrollBarStyle? byName(String? name) { + if (name != null) { + try { + return AndroidScrollBarStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidScrollBarStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidScrollBarStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 16777216: return 'SCROLLBARS_INSIDE_INSET'; @@ -202,4 +260,15 @@ class AndroidScrollBarStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart index c53faec73..7b2335906 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_content_inset_adjustment_behavior.g.dart @@ -66,20 +66,45 @@ class ScrollViewContentInsetAdjustmentBehavior { return null; } + /// Gets a possible [ScrollViewContentInsetAdjustmentBehavior] instance value with name [name]. + /// + /// Goes through [ScrollViewContentInsetAdjustmentBehavior.values] looking for a value with + /// name [name], as reported by [ScrollViewContentInsetAdjustmentBehavior.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollViewContentInsetAdjustmentBehavior? byName(String? name) { + if (name != null) { + try { + return ScrollViewContentInsetAdjustmentBehavior.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollViewContentInsetAdjustmentBehavior] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ScrollViewContentInsetAdjustmentBehavior.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -92,6 +117,17 @@ class ScrollViewContentInsetAdjustmentBehavior { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class used to configure how safe area insets are added to the adjusted content inset. @@ -162,20 +198,46 @@ class IOSUIScrollViewContentInsetAdjustmentBehavior { return null; } + /// Gets a possible [IOSUIScrollViewContentInsetAdjustmentBehavior] instance value with name [name]. + /// + /// Goes through [IOSUIScrollViewContentInsetAdjustmentBehavior.values] looking for a value with + /// name [name], as reported by [IOSUIScrollViewContentInsetAdjustmentBehavior.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIScrollViewContentInsetAdjustmentBehavior? byName(String? name) { + if (name != null) { + try { + return IOSUIScrollViewContentInsetAdjustmentBehavior.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIScrollViewContentInsetAdjustmentBehavior] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map + asNameMap() => { + for (final value + in IOSUIScrollViewContentInsetAdjustmentBehavior.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'ALWAYS'; @@ -188,4 +250,15 @@ class IOSUIScrollViewContentInsetAdjustmentBehavior { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart index 2f64bd585..291579327 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/scrollview_deceleration_rate.g.dart @@ -55,12 +55,54 @@ class ScrollViewDecelerationRate { return null; } + /// Gets a possible [ScrollViewDecelerationRate] instance value with name [name]. + /// + /// Goes through [ScrollViewDecelerationRate.values] looking for a value with + /// name [name], as reported by [ScrollViewDecelerationRate.name]. + /// Returns the first value with the given name, otherwise `null`. + static ScrollViewDecelerationRate? byName(String? name) { + if (name != null) { + try { + return ScrollViewDecelerationRate.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ScrollViewDecelerationRate] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ScrollViewDecelerationRate.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'FAST': + return 'FAST'; + case 'NORMAL': + return 'NORMAL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -125,12 +167,54 @@ class IOSUIScrollViewDecelerationRate { return null; } + /// Gets a possible [IOSUIScrollViewDecelerationRate] instance value with name [name]. + /// + /// Goes through [IOSUIScrollViewDecelerationRate.values] looking for a value with + /// name [name], as reported by [IOSUIScrollViewDecelerationRate.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSUIScrollViewDecelerationRate? byName(String? name) { + if (name != null) { + try { + return IOSUIScrollViewDecelerationRate.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSUIScrollViewDecelerationRate] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSUIScrollViewDecelerationRate.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'FAST': + return 'FAST'; + case 'NORMAL': + return 'NORMAL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart index 0f043309e..292de50ab 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/search_result_display_style.g.dart @@ -58,20 +58,44 @@ class SearchResultDisplayStyle { return null; } + /// Gets a possible [SearchResultDisplayStyle] instance value with name [name]. + /// + /// Goes through [SearchResultDisplayStyle.values] looking for a value with + /// name [name], as reported by [SearchResultDisplayStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static SearchResultDisplayStyle? byName(String? name) { + if (name != null) { + try { + return SearchResultDisplayStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SearchResultDisplayStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SearchResultDisplayStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'CURRENT_AND_TOTAL'; @@ -82,4 +106,15 @@ class SearchResultDisplayStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart index 958761f01..669077024 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'security_origin.g.dart'; ///An object that identifies the origin of a particular resource. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart index 4d0b53415..0afa630e7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/security_origin.g.dart @@ -20,7 +20,8 @@ class SecurityOrigin { {required this.host, required this.port, required this.protocol}); ///Gets a possible [SecurityOrigin] instance from a [Map] value. - static SecurityOrigin? fromMap(Map? map) { + static SecurityOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class SecurityOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "host": host, "port": port, @@ -71,7 +72,8 @@ class IOSWKSecurityOrigin { {required this.host, required this.port, required this.protocol}); ///Gets a possible [IOSWKSecurityOrigin] instance from a [Map] value. - static IOSWKSecurityOrigin? fromMap(Map? map) { + static IOSWKSecurityOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -84,7 +86,7 @@ class IOSWKSecurityOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "host": host, "port": port, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart index 43acdd425..4944cbe71 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/selection_granularity.g.dart @@ -54,12 +54,53 @@ class SelectionGranularity { return null; } + /// Gets a possible [SelectionGranularity] instance value with name [name]. + /// + /// Goes through [SelectionGranularity.values] looking for a value with + /// name [name], as reported by [SelectionGranularity.name]. + /// Returns the first value with the given name, otherwise `null`. + static SelectionGranularity? byName(String? name) { + if (name != null) { + try { + return SelectionGranularity.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SelectionGranularity] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in SelectionGranularity.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CHARACTER'; + case 0: + return 'DYNAMIC'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,13 +109,7 @@ class SelectionGranularity { @override String toString() { - switch (_value) { - case 1: - return 'CHARACTER'; - case 0: - return 'DYNAMIC'; - } - return _value.toString(); + return name(); } } @@ -128,12 +163,54 @@ class IOSWKSelectionGranularity { return null; } + /// Gets a possible [IOSWKSelectionGranularity] instance value with name [name]. + /// + /// Goes through [IOSWKSelectionGranularity.values] looking for a value with + /// name [name], as reported by [IOSWKSelectionGranularity.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKSelectionGranularity? byName(String? name) { + if (name != null) { + try { + return IOSWKSelectionGranularity.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKSelectionGranularity] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKSelectionGranularity.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'CHARACTER'; + case 0: + return 'DYNAMIC'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -142,12 +219,6 @@ class IOSWKSelectionGranularity { @override String toString() { - switch (_value) { - case 1: - return 'CHARACTER'; - case 0: - return 'DYNAMIC'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart index acfad82e5..e03d1e15a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import '../in_app_webview/platform_webview.dart'; import 'server_trust_auth_response_action.dart'; +import 'enum_method.dart'; part 'server_trust_auth_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart index 4975dd74a..fdd3840a5 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response.g.dart @@ -13,20 +13,30 @@ class ServerTrustAuthResponse { ServerTrustAuthResponse({this.action = ServerTrustAuthResponseAction.CANCEL}); ///Gets a possible [ServerTrustAuthResponse] instance from a [Map] value. - static ServerTrustAuthResponse? fromMap(Map? map) { + static ServerTrustAuthResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ServerTrustAuthResponse(); - instance.action = - ServerTrustAuthResponseAction.fromNativeValue(map['action']); + instance.action = switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ServerTrustAuthResponseAction.fromNativeValue(map['action']), + EnumMethod.value => + ServerTrustAuthResponseAction.fromValue(map['action']), + EnumMethod.name => ServerTrustAuthResponseAction.byName(map['action']) + }; return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "action": action?.toNativeValue(), + "action": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => action?.toNativeValue(), + EnumMethod.value => action?.toValue(), + EnumMethod.name => action?.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart index 70ac9817a..ebfdccf65 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_auth_response_action.g.dart @@ -54,12 +54,54 @@ class ServerTrustAuthResponseAction { return null; } + /// Gets a possible [ServerTrustAuthResponseAction] instance value with name [name]. + /// + /// Goes through [ServerTrustAuthResponseAction.values] looking for a value with + /// name [name], as reported by [ServerTrustAuthResponseAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static ServerTrustAuthResponseAction? byName(String? name) { + if (name != null) { + try { + return ServerTrustAuthResponseAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ServerTrustAuthResponseAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ServerTrustAuthResponseAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'CANCEL'; + case 1: + return 'PROCEED'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -68,12 +110,6 @@ class ServerTrustAuthResponseAction { @override String toString() { - switch (_value) { - case 0: - return 'CANCEL'; - case 1: - return 'PROCEED'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart index a1c66d596..86dd52b98 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_authentication_challenge.dart'; import 'url_protection_space.dart'; +import 'enum_method.dart'; part 'server_trust_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart index 9db07d9cc..9b12ef419 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/server_trust_challenge.g.dart @@ -13,21 +13,23 @@ class ServerTrustChallenge extends URLAuthenticationChallenge { : super(protectionSpace: protectionSpace); ///Gets a possible [ServerTrustChallenge] instance from a [Map] value. - static ServerTrustChallenge? fromMap(Map? map) { + static ServerTrustChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = ServerTrustChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart index 378e44959..c41616a30 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/should_allow_deprecated_tls_action.g.dart @@ -56,12 +56,54 @@ class ShouldAllowDeprecatedTLSAction { return null; } + /// Gets a possible [ShouldAllowDeprecatedTLSAction] instance value with name [name]. + /// + /// Goes through [ShouldAllowDeprecatedTLSAction.values] looking for a value with + /// name [name], as reported by [ShouldAllowDeprecatedTLSAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static ShouldAllowDeprecatedTLSAction? byName(String? name) { + if (name != null) { + try { + return ShouldAllowDeprecatedTLSAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ShouldAllowDeprecatedTLSAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ShouldAllowDeprecatedTLSAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -70,13 +112,7 @@ class ShouldAllowDeprecatedTLSAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } @@ -132,12 +168,54 @@ class IOSShouldAllowDeprecatedTLSAction { return null; } + /// Gets a possible [IOSShouldAllowDeprecatedTLSAction] instance value with name [name]. + /// + /// Goes through [IOSShouldAllowDeprecatedTLSAction.values] looking for a value with + /// name [name], as reported by [IOSShouldAllowDeprecatedTLSAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSShouldAllowDeprecatedTLSAction? byName(String? name) { + if (name != null) { + try { + return IOSShouldAllowDeprecatedTLSAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSShouldAllowDeprecatedTLSAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSShouldAllowDeprecatedTLSAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ALLOW'; + case 0: + return 'CANCEL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -146,12 +224,6 @@ class IOSShouldAllowDeprecatedTLSAction { @override String toString() { - switch (_value) { - case 1: - return 'ALLOW'; - case 0: - return 'CANCEL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart new file mode 100644 index 000000000..4cad89af7 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.dart @@ -0,0 +1,39 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import '../in_app_webview/platform_webview.dart'; +import 'show_file_chooser_request_mode.dart'; +import 'enum_method.dart'; + +part 'show_file_chooser_request.g.dart'; + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +@ExchangeableObject() +class ShowFileChooserRequest_ { + ///The file chooser mode. + final ShowFileChooserRequestMode_ mode; + + ///An array of acceptable MIME types. + ///The returned MIME type could be partial such as audio/*. + ///The array will be empty if no acceptable types are specified. + final List acceptTypes; + + ///Preference for a live media captured value (e. g. Camera, Microphone). + ///True indicates capture is enabled, false disabled. + ///Use [acceptTypes] to determine suitable capture devices. + final bool isCaptureEnabled; + + ///The title to use for this file selector. + ///If `null` a default title should be used. + final String? title; + + ///The file name of a default selection if specified, or `null`. + final String? filenameHint; + + ShowFileChooserRequest_({ + required this.mode, + required this.acceptTypes, + required this.isCaptureEnabled, + this.title, + this.filenameHint, + }); +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart new file mode 100644 index 000000000..14d951687 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request.g.dart @@ -0,0 +1,82 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_request.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +class ShowFileChooserRequest { + ///An array of acceptable MIME types. + ///The returned MIME type could be partial such as audio/*. + ///The array will be empty if no acceptable types are specified. + final List acceptTypes; + + ///The file name of a default selection if specified, or `null`. + final String? filenameHint; + + ///Preference for a live media captured value (e. g. Camera, Microphone). + ///True indicates capture is enabled, false disabled. + ///Use [acceptTypes] to determine suitable capture devices. + final bool isCaptureEnabled; + + ///The file chooser mode. + final ShowFileChooserRequestMode mode; + + ///The title to use for this file selector. + ///If `null` a default title should be used. + final String? title; + ShowFileChooserRequest( + {required this.acceptTypes, + this.filenameHint, + required this.isCaptureEnabled, + required this.mode, + this.title}); + + ///Gets a possible [ShowFileChooserRequest] instance from a [Map] value. + static ShowFileChooserRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ShowFileChooserRequest( + acceptTypes: List.from(map['acceptTypes']!.cast()), + filenameHint: map['filenameHint'], + isCaptureEnabled: map['isCaptureEnabled'], + mode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + ShowFileChooserRequestMode.fromNativeValue(map['mode']), + EnumMethod.value => ShowFileChooserRequestMode.fromValue(map['mode']), + EnumMethod.name => ShowFileChooserRequestMode.byName(map['mode']) + }!, + title: map['title'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "acceptTypes": acceptTypes, + "filenameHint": filenameHint, + "isCaptureEnabled": isCaptureEnabled, + "mode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => mode.toNativeValue(), + EnumMethod.value => mode.toValue(), + EnumMethod.name => mode.name() + }, + "title": title, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ShowFileChooserRequest{acceptTypes: $acceptTypes, filenameHint: $filenameHint, isCaptureEnabled: $isCaptureEnabled, mode: $mode, title: $title}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart new file mode 100644 index 000000000..dff40f408 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.dart @@ -0,0 +1,41 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'show_file_chooser_request.dart'; + +part 'show_file_chooser_request_mode.g.dart'; + +///It represents file chooser mode of a [ShowFileChooserRequest]. +@ExchangeableEnum() +class ShowFileChooserRequestMode_ { + // ignore: unused_field + final int _value; + // ignore: unused_field + final int? _nativeValue = null; + const ShowFileChooserRequestMode_._internal(this._value); + + ///Open single file. Requires that the file exists before allowing the user to pick it. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 0) + ]) + static const OPEN = const ShowFileChooserRequestMode_._internal(0); + + ///Like Open but allows multiple files to be selected. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 1) + ]) + static const OPEN_MULTIPLE = const ShowFileChooserRequestMode_._internal(1); + + ///Like Open but allows a folder to be selected. + ///The implementation should enumerate all files selected by this operation. + ///This feature is not supported at the moment. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 2) + ]) + static const OPEN_FOLDER = const ShowFileChooserRequestMode_._internal(2); + + ///Allows picking a nonexistent file and saving it. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(value: 3) + ]) + static const SAVE = const ShowFileChooserRequestMode_._internal(3); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart new file mode 100644 index 000000000..2785b69b7 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_request_mode.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_request_mode.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///It represents file chooser mode of a [ShowFileChooserRequest]. +class ShowFileChooserRequestMode { + final int _value; + final int? _nativeValue; + const ShowFileChooserRequestMode._internal(this._value, this._nativeValue); +// ignore: unused_element + factory ShowFileChooserRequestMode._internalMultiPlatform( + int value, Function nativeValue) => + ShowFileChooserRequestMode._internal(value, nativeValue()); + + ///Open single file. Requires that the file exists before allowing the user to pick it. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN = ShowFileChooserRequestMode._internalMultiPlatform(0, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 0; + default: + break; + } + return null; + }); + + ///Like Open but allows a folder to be selected. + ///The implementation should enumerate all files selected by this operation. + ///This feature is not supported at the moment. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN_FOLDER = + ShowFileChooserRequestMode._internalMultiPlatform(2, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 2; + default: + break; + } + return null; + }); + + ///Like Open but allows multiple files to be selected. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final OPEN_MULTIPLE = + ShowFileChooserRequestMode._internalMultiPlatform(1, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 1; + default: + break; + } + return null; + }); + + ///Allows picking a nonexistent file and saving it. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + static final SAVE = ShowFileChooserRequestMode._internalMultiPlatform(3, () { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return 3; + default: + break; + } + return null; + }); + + ///Set of all values of [ShowFileChooserRequestMode]. + static final Set values = [ + ShowFileChooserRequestMode.OPEN, + ShowFileChooserRequestMode.OPEN_FOLDER, + ShowFileChooserRequestMode.OPEN_MULTIPLE, + ShowFileChooserRequestMode.SAVE, + ].toSet(); + + ///Gets a possible [ShowFileChooserRequestMode] instance from [int] value. + static ShowFileChooserRequestMode? fromValue(int? value) { + if (value != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [ShowFileChooserRequestMode] instance from a native value. + static ShowFileChooserRequestMode? fromNativeValue(int? value) { + if (value != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [ShowFileChooserRequestMode] instance value with name [name]. + /// + /// Goes through [ShowFileChooserRequestMode.values] looking for a value with + /// name [name], as reported by [ShowFileChooserRequestMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static ShowFileChooserRequestMode? byName(String? name) { + if (name != null) { + try { + return ShowFileChooserRequestMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [ShowFileChooserRequestMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in ShowFileChooserRequestMode.values) + value.name(): value + }; + + ///Gets [int] value. + int toValue() => _value; + + ///Gets [int?] native value. + int? toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'OPEN'; + case 2: + return 'OPEN_FOLDER'; + case 1: + return 'OPEN_MULTIPLE'; + case 3: + return 'SAVE'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart new file mode 100644 index 000000000..88fc3ee7c --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.dart @@ -0,0 +1,22 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +import '../in_app_webview/platform_webview.dart'; +import 'enum_method.dart'; + +part 'show_file_chooser_response.g.dart'; + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +@ExchangeableObject() +class ShowFileChooserResponse_ { + ///Whether the file chooser request was handled by the client. + final bool handledByClient; + + ///The file paths of the selected files or `null` to cancel the request. + ///Each file path must be a valid file URI using the "file:" scheme. + final List? filePaths; + + ShowFileChooserResponse_({ + required this.handledByClient, + this.filePaths, + }); +} \ No newline at end of file diff --git a/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart new file mode 100644 index 000000000..66eeb7dea --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/show_file_chooser_response.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'show_file_chooser_response.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Class used in the [PlatformWebViewCreationParams.onShowFileChooser] method. +class ShowFileChooserResponse { + ///The file paths of the selected files or `null` to cancel the request. + ///Each file path must be a valid file URI using the "file:" scheme. + final List? filePaths; + + ///Whether the file chooser request was handled by the client. + final bool handledByClient; + ShowFileChooserResponse({this.filePaths, required this.handledByClient}); + + ///Gets a possible [ShowFileChooserResponse] instance from a [Map] value. + static ShowFileChooserResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { + if (map == null) { + return null; + } + final instance = ShowFileChooserResponse( + filePaths: map['filePaths'] != null + ? List.from(map['filePaths']!.cast()) + : null, + handledByClient: map['handledByClient'], + ); + return instance; + } + + ///Converts instance to a map. + Map toMap({EnumMethod? enumMethod}) { + return { + "filePaths": filePaths, + "handledByClient": handledByClient, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + @override + String toString() { + return 'ShowFileChooserResponse{filePaths: $filePaths, handledByClient: $handledByClient}'; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart index 041cfabd4..35c3a3a22 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../util.dart'; import '../x509_certificate/x509_certificate.dart'; import '../x509_certificate/asn1_distinguished_names.dart'; +import 'enum_method.dart'; import 'ssl_certificate_dname.dart'; @@ -34,7 +35,8 @@ class SslCertificate_ { this.x509Certificate}); ///Gets a possible [SslCertificate] instance from a [Map] value. - static SslCertificate? fromMap(Map? map) { + static SslCertificate? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -81,10 +83,12 @@ class SslCertificate_ { } return SslCertificate( - issuedBy: - SslCertificateDName.fromMap(map["issuedBy"]?.cast()), - issuedTo: - SslCertificateDName.fromMap(map["issuedTo"]?.cast()), + issuedBy: SslCertificateDName.fromMap( + map["issuedBy"]?.cast(), + enumMethod: enumMethod), + issuedTo: SslCertificateDName.fromMap( + map["issuedTo"]?.cast(), + enumMethod: enumMethod), validNotAfterDate: map["validNotAfterDate"] != null ? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]) : null, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart index d98b03127..8d548c6f8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate.g.dart @@ -30,7 +30,8 @@ class SslCertificate { this.x509Certificate}); ///Gets a possible [SslCertificate] instance from a [Map] value. - static SslCertificate? fromMap(Map? map) { + static SslCertificate? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -72,9 +73,11 @@ class SslCertificate { } return SslCertificate( issuedBy: SslCertificateDName.fromMap( - map["issuedBy"]?.cast()), + map["issuedBy"]?.cast(), + enumMethod: enumMethod), issuedTo: SslCertificateDName.fromMap( - map["issuedTo"]?.cast()), + map["issuedTo"]?.cast(), + enumMethod: enumMethod), validNotAfterDate: map["validNotAfterDate"] != null ? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]) : null, @@ -85,13 +88,13 @@ class SslCertificate { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "issuedBy": issuedBy?.toMap(), - "issuedTo": issuedTo?.toMap(), + "issuedBy": issuedBy?.toMap(enumMethod: enumMethod), + "issuedTo": issuedTo?.toMap(enumMethod: enumMethod), "validNotAfterDate": validNotAfterDate?.millisecondsSinceEpoch, "validNotBeforeDate": validNotBeforeDate?.millisecondsSinceEpoch, - "x509Certificate": x509Certificate?.toMap(), + "x509Certificate": x509Certificate?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart index fba647b10..79822a0fb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'ssl_certificate.dart'; part 'ssl_certificate_dname.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart index 3231eb69c..b6ac1c5c2 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_certificate_dname.g.dart @@ -23,7 +23,8 @@ class SslCertificateDName { {this.CName = "", this.DName = "", this.OName = "", this.UName = ""}); ///Gets a possible [SslCertificateDName] instance from a [Map] value. - static SslCertificateDName? fromMap(Map? map) { + static SslCertificateDName? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -36,7 +37,7 @@ class SslCertificateDName { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "CName": CName, "DName": DName, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart index 1971d8405..4a3d73923 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'ssl_error_type.dart'; part 'ssl_error.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart index 1f838bc3c..1aeec754b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error.g.dart @@ -32,23 +32,40 @@ class SslError { } ///Gets a possible [SslError] instance from a [Map] value. - static SslError? fromMap(Map? map) { + static SslError? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = SslError( - androidError: AndroidSslError.fromNativeValue(map['code']), - code: SslErrorType.fromNativeValue(map['code']), - iosError: IOSSslError.fromNativeValue(map['code']), + androidError: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => AndroidSslError.fromNativeValue(map['code']), + EnumMethod.value => AndroidSslError.fromValue(map['code']), + EnumMethod.name => AndroidSslError.byName(map['code']) + }, + code: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => SslErrorType.fromNativeValue(map['code']), + EnumMethod.value => SslErrorType.fromValue(map['code']), + EnumMethod.name => SslErrorType.byName(map['code']) + }, + iosError: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSSslError.fromNativeValue(map['code']), + EnumMethod.value => IOSSslError.fromValue(map['code']), + EnumMethod.name => IOSSslError.byName(map['code']) + }, message: map['message'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "code": code?.toNativeValue(), + "code": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => code?.toNativeValue(), + EnumMethod.value => code?.toValue(), + EnumMethod.name => code?.name() + }, "message": message, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart index b4db1bd57..a8348cdb3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.dart @@ -32,7 +32,12 @@ class SslErrorType_ { apiName: 'SslError.SSL_EXPIRED', apiUrl: 'https://developer.android.com/reference/android/net/http/SslError#SSL_EXPIRED', - value: 1) + value: 1), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 2) ]) static const EXPIRED = SslErrorType_._internal('EXPIRED'); @@ -82,7 +87,12 @@ class SslErrorType_ { apiName: 'SecTrustResultType.invalid', apiUrl: 'https://developer.apple.com/documentation/security/sectrustresulttype/invalid', - value: 0) + value: 0), + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 5) ]) static const INVALID = SslErrorType_._internal('INVALID'); @@ -176,7 +186,9 @@ class SslErrorType_ { ///Indicates a failure other than that of trust evaluation. /// ///This value indicates that evaluation failed for some other reason. - ///This can be caused by either a revoked certificate or by OS-level errors that are unrelated to the certificates themselves. + /// + ///On iOS and macOS, this can be caused by either a revoked certificate or + ///by OS-level errors that are unrelated to the certificates themselves. @EnumSupportedPlatforms(platforms: [ EnumIOSPlatform( apiName: 'SecTrustResultType.otherError', @@ -187,9 +199,37 @@ class SslErrorType_ { apiName: 'SecTrustResultType.otherError', apiUrl: 'https://developer.apple.com/documentation/security/sectrustresulttype/othererror', - value: 7) + value: 7), + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 3) ]) static const OTHER_ERROR = SslErrorType_._internal('OTHER_ERROR'); + + ///Indicates that the SSL certificate common name does not match the web address. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: + 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 1) + ]) + static const COMMON_NAME_IS_INCORRECT = + SslErrorType_._internal('COMMON_NAME_IS_INCORRECT'); + + ///Indicates that the SSL certificate has been revoked. + @EnumSupportedPlatforms(platforms: [ + EnumWindowsPlatform( + apiName: 'COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status', + value: 4) + ]) + static const REVOKED = SslErrorType_._internal('REVOKED'); } ///Class that represents the Android-specific primary error associated to the server SSL certificate. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart index e4043d9fc..ba0d2204f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ssl_error_type.g.dart @@ -17,6 +17,21 @@ class SslErrorType { String value, Function nativeValue) => SslErrorType._internal(value, nativeValue()); + ///Indicates that the SSL certificate common name does not match the web address. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) + static final COMMON_NAME_IS_INCORRECT = + SslErrorType._internalMultiPlatform('COMMON_NAME_IS_INCORRECT', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 1; + default: + break; + } + return null; + }); + ///The date of the certificate is invalid. /// ///**Officially Supported Platforms/Implementations**: @@ -58,10 +73,13 @@ class SslErrorType { /// ///**Officially Supported Platforms/Implementations**: ///- Android native WebView ([Official API - SslError.SSL_EXPIRED](https://developer.android.com/reference/android/net/http/SslError#SSL_EXPIRED)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final EXPIRED = SslErrorType._internalMultiPlatform('EXPIRED', () { switch (defaultTargetPlatform) { case TargetPlatform.android: return 1; + case TargetPlatform.windows: + return 2; default: break; } @@ -112,6 +130,7 @@ class SslErrorType { ///- Android native WebView ([Official API - SslError.SSL_INVALID](https://developer.android.com/reference/android/net/http/SslError#SSL_INVALID)) ///- iOS ([Official API - SecTrustResultType.invalid](https://developer.apple.com/documentation/security/sectrustresulttype/invalid)) ///- MacOS ([Official API - SecTrustResultType.invalid](https://developer.apple.com/documentation/security/sectrustresulttype/invalid)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final INVALID = SslErrorType._internalMultiPlatform('INVALID', () { switch (defaultTargetPlatform) { case TargetPlatform.android: @@ -120,6 +139,8 @@ class SslErrorType { return 0; case TargetPlatform.macOS: return 0; + case TargetPlatform.windows: + return 5; default: break; } @@ -144,11 +165,14 @@ class SslErrorType { ///Indicates a failure other than that of trust evaluation. /// ///This value indicates that evaluation failed for some other reason. - ///This can be caused by either a revoked certificate or by OS-level errors that are unrelated to the certificates themselves. + /// + ///On iOS and macOS, this can be caused by either a revoked certificate or + ///by OS-level errors that are unrelated to the certificates themselves. /// ///**Officially Supported Platforms/Implementations**: ///- iOS ([Official API - SecTrustResultType.otherError](https://developer.apple.com/documentation/security/sectrustresulttype/othererror)) ///- MacOS ([Official API - SecTrustResultType.otherError](https://developer.apple.com/documentation/security/sectrustresulttype/othererror)) + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) static final OTHER_ERROR = SslErrorType._internalMultiPlatform('OTHER_ERROR', () { switch (defaultTargetPlatform) { @@ -156,6 +180,8 @@ class SslErrorType { return 7; case TargetPlatform.macOS: return 7; + case TargetPlatform.windows: + return 3; default: break; } @@ -188,6 +214,20 @@ class SslErrorType { return null; }); + ///Indicates that the SSL certificate has been revoked. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2849.39#corewebview2_web_error_status)) + static final REVOKED = SslErrorType._internalMultiPlatform('REVOKED', () { + switch (defaultTargetPlatform) { + case TargetPlatform.windows: + return 4; + default: + break; + } + return null; + }); + ///Indicates the evaluation succeeded and the certificate is implicitly trusted, but user intent was not explicitly specified. /// ///This value indicates that evaluation reached an (implicitly trusted) anchor certificate without any evaluation failures, @@ -229,6 +269,7 @@ class SslErrorType { ///Set of all values of [SslErrorType]. static final Set values = [ + SslErrorType.COMMON_NAME_IS_INCORRECT, SslErrorType.DATE_INVALID, SslErrorType.DENY, SslErrorType.EXPIRED, @@ -238,6 +279,7 @@ class SslErrorType { SslErrorType.NOT_YET_VALID, SslErrorType.OTHER_ERROR, SslErrorType.RECOVERABLE_TRUST_FAILURE, + SslErrorType.REVOKED, SslErrorType.UNSPECIFIED, SslErrorType.UNTRUSTED, ].toSet(); @@ -268,12 +310,74 @@ class SslErrorType { return null; } + /// Gets a possible [SslErrorType] instance value with name [name]. + /// + /// Goes through [SslErrorType.values] looking for a value with + /// name [name], as reported by [SslErrorType.name]. + /// Returns the first value with the given name, otherwise `null`. + static SslErrorType? byName(String? name) { + if (name != null) { + try { + return SslErrorType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [SslErrorType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in SslErrorType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'COMMON_NAME_IS_INCORRECT': + return 'COMMON_NAME_IS_INCORRECT'; + case 'DATE_INVALID': + return 'DATE_INVALID'; + case 'DENY': + return 'DENY'; + case 'EXPIRED': + return 'EXPIRED'; + case 'FATAL_TRUST_FAILURE': + return 'FATAL_TRUST_FAILURE'; + case 'IDMISMATCH': + return 'IDMISMATCH'; + case 'INVALID': + return 'INVALID'; + case 'NOT_YET_VALID': + return 'NOT_YET_VALID'; + case 'OTHER_ERROR': + return 'OTHER_ERROR'; + case 'RECOVERABLE_TRUST_FAILURE': + return 'RECOVERABLE_TRUST_FAILURE'; + case 'REVOKED': + return 'REVOKED'; + case 'UNSPECIFIED': + return 'UNSPECIFIED'; + case 'UNTRUSTED': + return 'UNTRUSTED'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -353,20 +457,43 @@ class AndroidSslError { return null; } + /// Gets a possible [AndroidSslError] instance value with name [name]. + /// + /// Goes through [AndroidSslError.values] looking for a value with + /// name [name], as reported by [AndroidSslError.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidSslError? byName(String? name) { + if (name != null) { + try { + return AndroidSslError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidSslError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in AndroidSslError.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'SSL_DATE_INVALID'; @@ -383,6 +510,17 @@ class AndroidSslError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///Class that represents the iOS-specific primary error associated to the server SSL certificate. @@ -451,20 +589,43 @@ class IOSSslError { return null; } + /// Gets a possible [IOSSslError] instance value with name [name]. + /// + /// Goes through [IOSSslError.values] looking for a value with + /// name [name], as reported by [IOSSslError.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSSslError? byName(String? name) { + if (name != null) { + try { + return IOSSslError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSSslError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in IOSSslError.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 3: return 'DENY'; @@ -481,4 +642,15 @@ class IOSSslError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart index 3487a1843..5943c03f6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/tracing_category.g.dart @@ -87,20 +87,43 @@ class TracingCategory { return null; } + /// Gets a possible [TracingCategory] instance value with name [name]. + /// + /// Goes through [TracingCategory.values] looking for a value with + /// name [name], as reported by [TracingCategory.name]. + /// Returns the first value with the given name, otherwise `null`. + static TracingCategory? byName(String? name) { + if (name != null) { + try { + return TracingCategory.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TracingCategory] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in TracingCategory.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'CATEGORIES_ALL'; @@ -121,4 +144,15 @@ class TracingCategory { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart index 3552fe3d1..1dab01ed6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/tracing_mode.g.dart @@ -59,12 +59,52 @@ class TracingMode { return null; } + /// Gets a possible [TracingMode] instance value with name [name]. + /// + /// Goes through [TracingMode.values] looking for a value with + /// name [name], as reported by [TracingMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static TracingMode? byName(String? name) { + if (name != null) { + try { + return TracingMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TracingMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in TracingMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'RECORD_CONTINUOUSLY'; + case 0: + return 'RECORD_UNTIL_FULL'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -73,12 +113,6 @@ class TracingMode { @override String toString() { - switch (_value) { - case 1: - return 'RECORD_CONTINUOUSLY'; - case 0: - return 'RECORD_UNTIL_FULL'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart index ef160eebe..829b9b6bb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.dart @@ -1,5 +1,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'trusted_web_activity_display_mode.dart'; part 'trusted_web_activity_default_display_mode.g.dart'; @@ -13,7 +14,7 @@ class TrustedWebActivityDefaultDisplayMode_ @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart index 395866eba..634cbd4f6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_default_display_mode.g.dart @@ -13,14 +13,14 @@ class TrustedWebActivityDefaultDisplayMode static final String _type = "DEFAULT_MODE"; TrustedWebActivityDefaultDisplayMode(); @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart index 2b7e54ab9..97405a532 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'trusted_web_activity_display_mode.g.dart'; ///Class that represents display mode of a Trusted Web Activity. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart index 064073015..ed97b55be 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_display_mode.g.dart @@ -11,7 +11,7 @@ abstract class TrustedWebActivityDisplayMode { TrustedWebActivityDisplayMode(); ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return {}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart index ead2d1898..dbddaec14 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'trusted_web_activity_display_mode.dart'; import 'layout_in_display_cutout_mode.dart'; +import 'enum_method.dart'; part 'trusted_web_activity_immersive_display_mode.g.dart'; @@ -35,7 +36,7 @@ class TrustedWebActivityImmersiveDisplayMode_ @ExchangeableObjectMethod(toMapMergeWith: true) // ignore: unused_element - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart index a5370c9eb..41c9f4b55 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_immersive_display_mode.g.dart @@ -33,32 +33,52 @@ class TrustedWebActivityImmersiveDisplayMode ///Gets a possible [TrustedWebActivityImmersiveDisplayMode] instance from a [Map] value. static TrustedWebActivityImmersiveDisplayMode? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = TrustedWebActivityImmersiveDisplayMode( isSticky: map['isSticky'], - layoutInDisplayCutoutMode: + layoutInDisplayCutoutMode: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => AndroidLayoutInDisplayCutoutMode.fromNativeValue( map['displayCutoutMode']), + EnumMethod.value => + AndroidLayoutInDisplayCutoutMode.fromValue(map['displayCutoutMode']), + EnumMethod.name => + AndroidLayoutInDisplayCutoutMode.byName(map['displayCutoutMode']) + }, ); - instance.displayCutoutMode = - LayoutInDisplayCutoutMode.fromNativeValue(map['displayCutoutMode'])!; + if (map['displayCutoutMode'] != null) { + instance.displayCutoutMode = + switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + LayoutInDisplayCutoutMode.fromNativeValue(map['displayCutoutMode']), + EnumMethod.value => + LayoutInDisplayCutoutMode.fromValue(map['displayCutoutMode']), + EnumMethod.name => + LayoutInDisplayCutoutMode.byName(map['displayCutoutMode']) + }!; + } return instance; } @ExchangeableObjectMethod(toMapMergeWith: true) - Map _toMapMergeWith() { + Map _toMapMergeWith({EnumMethod? enumMethod}) { return {"type": _type}; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "displayCutoutMode": displayCutoutMode.toNativeValue(), + "displayCutoutMode": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => displayCutoutMode.toNativeValue(), + EnumMethod.value => displayCutoutMode.toValue(), + EnumMethod.name => displayCutoutMode.name() + }, "isSticky": isSticky, - ..._toMapMergeWith(), + ..._toMapMergeWith(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart index 1d7c4676b..f17fdc4b8 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/trusted_web_activity_screen_orientation.g.dart @@ -104,20 +104,45 @@ class TrustedWebActivityScreenOrientation { return null; } + /// Gets a possible [TrustedWebActivityScreenOrientation] instance value with name [name]. + /// + /// Goes through [TrustedWebActivityScreenOrientation.values] looking for a value with + /// name [name], as reported by [TrustedWebActivityScreenOrientation.name]. + /// Returns the first value with the given name, otherwise `null`. + static TrustedWebActivityScreenOrientation? byName(String? name) { + if (name != null) { + try { + return TrustedWebActivityScreenOrientation.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [TrustedWebActivityScreenOrientation] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in TrustedWebActivityScreenOrientation.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 5: return 'ANY'; @@ -140,4 +165,15 @@ class TrustedWebActivityScreenOrientation { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart index 31f16b6cb..79bb0af4c 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'ui_event_attribution.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart index 3fda296e9..9b85f14ec 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_event_attribution.g.dart @@ -35,7 +35,8 @@ class UIEventAttribution { required this.purchaser}); ///Gets a possible [UIEventAttribution] instance from a [Map] value. - static UIEventAttribution? fromMap(Map? map) { + static UIEventAttribution? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -49,7 +50,7 @@ class UIEventAttribution { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "destinationURL": destinationURL.toString(), "purchaser": purchaser, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart index bafb8a76f..7d6f74674 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.dart @@ -1,7 +1,8 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'ui_image.g.dart'; ///Class that represents an object that manages iOS and MacOS image data in your app. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart index 3071d8a0d..9f17ed58b 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/ui_image.g.dart @@ -32,7 +32,7 @@ class UIImage { } ///Gets a possible [UIImage] instance from a [Map] value. - static UIImage? fromMap(Map? map) { + static UIImage? fromMap(Map? map, {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -47,7 +47,7 @@ class UIImage { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "data": data, "name": name, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart index 3a0871bcf..fd6277469 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/underline_style.g.dart @@ -82,20 +82,43 @@ class UnderlineStyle { return null; } + /// Gets a possible [UnderlineStyle] instance value with name [name]. + /// + /// Goes through [UnderlineStyle.values] looking for a value with + /// name [name], as reported by [UnderlineStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static UnderlineStyle? byName(String? name) { + if (name != null) { + try { + return UnderlineStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UnderlineStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in UnderlineStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 32768: return 'BY_WORD'; @@ -118,6 +141,17 @@ class UnderlineStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants for the underline style and strikethrough style attribute keys. @@ -198,20 +232,44 @@ class IOSNSUnderlineStyle { return null; } + /// Gets a possible [IOSNSUnderlineStyle] instance value with name [name]. + /// + /// Goes through [IOSNSUnderlineStyle.values] looking for a value with + /// name [name], as reported by [IOSNSUnderlineStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSUnderlineStyle? byName(String? name) { + if (name != null) { + try { + return IOSNSUnderlineStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSUnderlineStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSUnderlineStyle.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 32768: return 'BY_WORD'; @@ -234,4 +292,15 @@ class IOSNSUnderlineStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart index aefb1df06..1d2f73aef 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'url_protection_space.dart'; +import 'enum_method.dart'; part 'url_authentication_challenge.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart index e62ddc4c6..b0258fea6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_authentication_challenge.g.dart @@ -14,21 +14,23 @@ class URLAuthenticationChallenge { URLAuthenticationChallenge({required this.protectionSpace}); ///Gets a possible [URLAuthenticationChallenge] instance from a [Map] value. - static URLAuthenticationChallenge? fromMap(Map? map) { + static URLAuthenticationChallenge? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLAuthenticationChallenge( protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast())!, + map['protectionSpace']?.cast(), + enumMethod: enumMethod)!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "protectionSpace": protectionSpace.toMap(), + "protectionSpace": protectionSpace.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart index 0bb91451e..54eddc6dc 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.dart @@ -1,13 +1,14 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../x509_certificate/x509_certificate.dart'; import 'url_credential_persistence.dart'; +import 'enum_method.dart'; part 'url_credential.g.dart'; -List? _certificatesDeserializer(dynamic value) { +List? _certificatesDeserializer(dynamic value, + {EnumMethod? enumMethod}) { List? certificates; if (value != null) { certificates = []; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart index 6a135cb0a..90b481db4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential.g.dart @@ -50,28 +50,48 @@ class URLCredential { } ///Gets a possible [URLCredential] instance from a [Map] value. - static URLCredential? fromMap(Map? map) { + static URLCredential? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLCredential( - certificates: _certificatesDeserializer(map['certificates']), - iosCertificates: _certificatesDeserializer(map['certificates']), - iosPersistence: + certificates: _certificatesDeserializer(map['certificates'], + enumMethod: enumMethod), + iosCertificates: _certificatesDeserializer(map['certificates'], + enumMethod: enumMethod), + iosPersistence: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSURLCredentialPersistence.fromNativeValue(map['persistence']), + EnumMethod.value => + IOSURLCredentialPersistence.fromValue(map['persistence']), + EnumMethod.name => + IOSURLCredentialPersistence.byName(map['persistence']) + }, password: map['password'], - persistence: URLCredentialPersistence.fromNativeValue(map['persistence']), + persistence: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLCredentialPersistence.fromNativeValue(map['persistence']), + EnumMethod.value => + URLCredentialPersistence.fromValue(map['persistence']), + EnumMethod.name => URLCredentialPersistence.byName(map['persistence']) + }, username: map['username'], ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "certificates": certificates?.map((e) => e.toMap()).toList(), + "certificates": + certificates?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), "password": password, - "persistence": persistence?.toNativeValue(), + "persistence": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => persistence?.toNativeValue(), + EnumMethod.value => persistence?.toValue(), + EnumMethod.name => persistence?.name() + }, "username": username, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart index 75b4d5535..dd5a05f6a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_credential_persistence.g.dart @@ -63,20 +63,44 @@ class URLCredentialPersistence { return null; } + /// Gets a possible [URLCredentialPersistence] instance value with name [name]. + /// + /// Goes through [URLCredentialPersistence.values] looking for a value with + /// name [name], as reported by [URLCredentialPersistence.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLCredentialPersistence? byName(String? name) { + if (name != null) { + try { + return URLCredentialPersistence.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLCredentialPersistence] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLCredentialPersistence.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'FOR_SESSION'; @@ -89,6 +113,17 @@ class URLCredentialPersistence { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific class that represents the constants that specify how long the credential will be kept. @@ -150,20 +185,45 @@ class IOSURLCredentialPersistence { return null; } + /// Gets a possible [IOSURLCredentialPersistence] instance value with name [name]. + /// + /// Goes through [IOSURLCredentialPersistence.values] looking for a value with + /// name [name], as reported by [IOSURLCredentialPersistence.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLCredentialPersistence? byName(String? name) { + if (name != null) { + try { + return IOSURLCredentialPersistence.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLCredentialPersistence] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLCredentialPersistence.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'FOR_SESSION'; @@ -176,4 +236,15 @@ class IOSURLCredentialPersistence { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart index 2e18696b3..a2fd0e721 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.dart @@ -1,5 +1,4 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../x509_certificate/x509_certificate.dart'; @@ -7,10 +6,12 @@ import 'url_protection_space_proxy_type.dart'; import 'url_protection_space_authentication_method.dart'; import 'ssl_error.dart'; import 'ssl_certificate.dart'; +import 'enum_method.dart'; part 'url_protection_space.g.dart'; -List? _distinguishedNamesDeserializer(dynamic value) { +List? _distinguishedNamesDeserializer(dynamic value, + {EnumMethod? enumMethod}) { List? distinguishedNames; if (value != null) { distinguishedNames = []; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart index 9af49ce84..55ad5df09 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space.g.dart @@ -105,50 +105,90 @@ class URLProtectionSpace { } ///Gets a possible [URLProtectionSpace] instance from a [Map] value. - static URLProtectionSpace? fromMap(Map? map) { + static URLProtectionSpace? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLProtectionSpace( - authenticationMethod: + authenticationMethod: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => URLProtectionSpaceAuthenticationMethod.fromNativeValue( map['authenticationMethod']), - distinguishedNames: - _distinguishedNamesDeserializer(map['distinguishedNames']), + EnumMethod.value => URLProtectionSpaceAuthenticationMethod.fromValue( + map['authenticationMethod']), + EnumMethod.name => URLProtectionSpaceAuthenticationMethod.byName( + map['authenticationMethod']) + }, + distinguishedNames: _distinguishedNamesDeserializer( + map['distinguishedNames'], + enumMethod: enumMethod), host: map['host'], - iosAuthenticationMethod: + iosAuthenticationMethod: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSNSURLProtectionSpaceAuthenticationMethod.fromNativeValue( map['authenticationMethod']), - iosDistinguishedNames: - _distinguishedNamesDeserializer(map['distinguishedNames']), - iosProxyType: + EnumMethod.value => + IOSNSURLProtectionSpaceAuthenticationMethod.fromValue( + map['authenticationMethod']), + EnumMethod.name => IOSNSURLProtectionSpaceAuthenticationMethod.byName( + map['authenticationMethod']) + }, + iosDistinguishedNames: _distinguishedNamesDeserializer( + map['distinguishedNames'], + enumMethod: enumMethod), + iosProxyType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSNSURLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + EnumMethod.value => + IOSNSURLProtectionSpaceProxyType.fromValue(map['proxyType']), + EnumMethod.name => + IOSNSURLProtectionSpaceProxyType.byName(map['proxyType']) + }, iosReceivesCredentialSecurely: map['receivesCredentialSecurely'], port: map['port'], protocol: map['protocol'], - proxyType: URLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + proxyType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLProtectionSpaceProxyType.fromNativeValue(map['proxyType']), + EnumMethod.value => + URLProtectionSpaceProxyType.fromValue(map['proxyType']), + EnumMethod.name => URLProtectionSpaceProxyType.byName(map['proxyType']) + }, realm: map['realm'], receivesCredentialSecurely: map['receivesCredentialSecurely'], sslCertificate: SslCertificate.fromMap( - map['sslCertificate']?.cast()), - sslError: SslError.fromMap(map['sslError']?.cast()), + map['sslCertificate']?.cast(), + enumMethod: enumMethod), + sslError: SslError.fromMap(map['sslError']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "authenticationMethod": authenticationMethod?.toNativeValue(), - "distinguishedNames": distinguishedNames?.map((e) => e.toMap()).toList(), + "authenticationMethod": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => authenticationMethod?.toNativeValue(), + EnumMethod.value => authenticationMethod?.toValue(), + EnumMethod.name => authenticationMethod?.name() + }, + "distinguishedNames": distinguishedNames + ?.map((e) => e.toMap(enumMethod: enumMethod)) + .toList(), "host": host, "port": port, "protocol": protocol, - "proxyType": proxyType?.toNativeValue(), + "proxyType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => proxyType?.toNativeValue(), + EnumMethod.value => proxyType?.toValue(), + EnumMethod.name => proxyType?.name() + }, "realm": realm, "receivesCredentialSecurely": receivesCredentialSecurely, - "sslCertificate": sslCertificate?.toMap(), - "sslError": sslError?.toMap(), + "sslCertificate": sslCertificate?.toMap(enumMethod: enumMethod), + "sslError": sslError?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart index a8f318ead..16e75e5ee 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_authentication_method.g.dart @@ -78,12 +78,58 @@ class URLProtectionSpaceAuthenticationMethod { return null; } + /// Gets a possible [URLProtectionSpaceAuthenticationMethod] instance value with name [name]. + /// + /// Goes through [URLProtectionSpaceAuthenticationMethod.values] looking for a value with + /// name [name], as reported by [URLProtectionSpaceAuthenticationMethod.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLProtectionSpaceAuthenticationMethod? byName(String? name) { + if (name != null) { + try { + return URLProtectionSpaceAuthenticationMethod.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLProtectionSpaceAuthenticationMethod] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLProtectionSpaceAuthenticationMethod.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLAuthenticationMethodClientCertificate': + return 'NSURL_AUTHENTICATION_METHOD_CLIENT_CERTIFICATE'; + case 'NSURLAuthenticationMethodNegotiate': + return 'NSURL_AUTHENTICATION_METHOD_NEGOTIATE'; + case 'NSURLAuthenticationMethodNTLM': + return 'NSURL_AUTHENTICATION_METHOD_NTLM'; + case 'NSURLAuthenticationMethodServerTrust': + return 'NSURL_AUTHENTICATION_METHOD_SERVER_TRUST'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -172,12 +218,58 @@ class IOSNSURLProtectionSpaceAuthenticationMethod { return null; } + /// Gets a possible [IOSNSURLProtectionSpaceAuthenticationMethod] instance value with name [name]. + /// + /// Goes through [IOSNSURLProtectionSpaceAuthenticationMethod.values] looking for a value with + /// name [name], as reported by [IOSNSURLProtectionSpaceAuthenticationMethod.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSURLProtectionSpaceAuthenticationMethod? byName(String? name) { + if (name != null) { + try { + return IOSNSURLProtectionSpaceAuthenticationMethod.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSURLProtectionSpaceAuthenticationMethod] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSURLProtectionSpaceAuthenticationMethod.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLAuthenticationMethodClientCertificate': + return 'NSURL_AUTHENTICATION_METHOD_CLIENT_CERTIFICATE'; + case 'NSURLAuthenticationMethodNegotiate': + return 'NSURL_AUTHENTICATION_METHOD_NEGOTIATE'; + case 'NSURLAuthenticationMethodNTLM': + return 'NSURL_AUTHENTICATION_METHOD_NTLM'; + case 'NSURLAuthenticationMethodServerTrust': + return 'NSURL_AUTHENTICATION_METHOD_SERVER_TRUST'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart index b5a67de50..6ca648247 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'url_protection_space.dart'; import 'url_credential.dart'; import '../platform_http_auth_credentials_database.dart'; +import 'enum_method.dart'; part 'url_protection_space_http_auth_credentials.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart index 744177cb3..9f1ec6d93 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_http_auth_credentials.g.dart @@ -19,26 +19,30 @@ class URLProtectionSpaceHttpAuthCredentials { ///Gets a possible [URLProtectionSpaceHttpAuthCredentials] instance from a [Map] value. static URLProtectionSpaceHttpAuthCredentials? fromMap( - Map? map) { + Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = URLProtectionSpaceHttpAuthCredentials( credentials: map['credentials'] != null - ? List.from(map['credentials'] - .map((e) => URLCredential.fromMap(e?.cast())!)) + ? List.from(map['credentials'].map((e) => + URLCredential.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, protectionSpace: URLProtectionSpace.fromMap( - map['protectionSpace']?.cast()), + map['protectionSpace']?.cast(), + enumMethod: enumMethod), ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "credentials": credentials?.map((e) => e.toMap()).toList(), - "protectionSpace": protectionSpace?.toMap(), + "credentials": + credentials?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "protectionSpace": protectionSpace?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart index e174e5823..b234553c7 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_protection_space_proxy_type.g.dart @@ -70,12 +70,58 @@ class URLProtectionSpaceProxyType { return null; } + /// Gets a possible [URLProtectionSpaceProxyType] instance value with name [name]. + /// + /// Goes through [URLProtectionSpaceProxyType.values] looking for a value with + /// name [name], as reported by [URLProtectionSpaceProxyType.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLProtectionSpaceProxyType? byName(String? name) { + if (name != null) { + try { + return URLProtectionSpaceProxyType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLProtectionSpaceProxyType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLProtectionSpaceProxyType.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLProtectionSpaceFTPProxy': + return 'URL_PROTECTION_SPACE_FTP_PROXY'; + case 'NSURLProtectionSpaceHTTPSProxy': + return 'URL_PROTECTION_SPACE_HTTPS_PROXY'; + case 'NSURLProtectionSpaceHTTPProxy': + return 'URL_PROTECTION_SPACE_HTTP_PROXY'; + case 'NSURLProtectionSpaceSOCKSProxy': + return 'URL_PROTECTION_SPACE_SOCKS_PROXY'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -155,12 +201,58 @@ class IOSNSURLProtectionSpaceProxyType { return null; } + /// Gets a possible [IOSNSURLProtectionSpaceProxyType] instance value with name [name]. + /// + /// Goes through [IOSNSURLProtectionSpaceProxyType.values] looking for a value with + /// name [name], as reported by [IOSNSURLProtectionSpaceProxyType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSNSURLProtectionSpaceProxyType? byName(String? name) { + if (name != null) { + try { + return IOSNSURLProtectionSpaceProxyType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSNSURLProtectionSpaceProxyType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSNSURLProtectionSpaceProxyType.values) + value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'NSURLProtectionSpaceFTPProxy': + return 'NSURL_PROTECTION_SPACE_FTP_PROXY'; + case 'NSURLProtectionSpaceHTTPSProxy': + return 'NSURL_PROTECTION_SPACE_HTTPS_PROXY'; + case 'NSURLProtectionSpaceSOCKSProxy': + return 'NSURL_PROTECTION_SPACE_SOCKS_PROXY'; + case 'NSURLProtectionSpaceHTTPProxy': + return 'NSUR_PROTECTION_SPACE_HTTP_PROXY'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart index e737a6f7e..ed66b6dc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request.dart @@ -5,6 +5,7 @@ import '../web_uri.dart'; import 'url_request_cache_policy.dart'; import 'url_request_network_service_type.dart'; import 'url_request_attribution.dart'; +import 'enum_method.dart'; part 'url_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart index e938c53b4..9c99ea0c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request.g.dart @@ -189,7 +189,8 @@ class URLRequest { } ///Gets a possible [URLRequest] instance from a [Map] value. - static URLRequest? fromMap(Map? map) { + static URLRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -198,33 +199,61 @@ class URLRequest { allowsConstrainedNetworkAccess: map['allowsConstrainedNetworkAccess'], allowsExpensiveNetworkAccess: map['allowsExpensiveNetworkAccess'], assumesHTTP3Capable: map['assumesHTTP3Capable'], - attribution: URLRequestAttribution.fromNativeValue(map['attribution']), + attribution: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLRequestAttribution.fromNativeValue(map['attribution']), + EnumMethod.value => URLRequestAttribution.fromValue(map['attribution']), + EnumMethod.name => URLRequestAttribution.byName(map['attribution']) + }, body: map['body'] != null ? Uint8List.fromList(map['body'].cast()) : null, - cachePolicy: URLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + cachePolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + URLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + EnumMethod.value => URLRequestCachePolicy.fromValue(map['cachePolicy']), + EnumMethod.name => URLRequestCachePolicy.byName(map['cachePolicy']) + }, headers: map['headers']?.cast(), httpShouldHandleCookies: map['httpShouldHandleCookies'], httpShouldUsePipelining: map['httpShouldUsePipelining'], iosAllowsCellularAccess: map['allowsCellularAccess'], iosAllowsConstrainedNetworkAccess: map['allowsConstrainedNetworkAccess'], iosAllowsExpensiveNetworkAccess: map['allowsExpensiveNetworkAccess'], - iosCachePolicy: + iosCachePolicy: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => IOSURLRequestCachePolicy.fromNativeValue(map['cachePolicy']), + EnumMethod.value => + IOSURLRequestCachePolicy.fromValue(map['cachePolicy']), + EnumMethod.name => IOSURLRequestCachePolicy.byName(map['cachePolicy']) + }, iosHttpShouldHandleCookies: map['httpShouldHandleCookies'], iosHttpShouldUsePipelining: map['httpShouldUsePipelining'], iosMainDocumentURL: map['mainDocumentURL'] != null ? Uri.tryParse(map['mainDocumentURL']) : null, - iosNetworkServiceType: IOSURLRequestNetworkServiceType.fromNativeValue( - map['networkServiceType']), + iosNetworkServiceType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + IOSURLRequestNetworkServiceType.fromNativeValue( + map['networkServiceType']), + EnumMethod.value => + IOSURLRequestNetworkServiceType.fromValue(map['networkServiceType']), + EnumMethod.name => + IOSURLRequestNetworkServiceType.byName(map['networkServiceType']) + }, iosTimeoutInterval: map['timeoutInterval'], mainDocumentURL: map['mainDocumentURL'] != null ? WebUri(map['mainDocumentURL']) : null, method: map['method'], - networkServiceType: URLRequestNetworkServiceType.fromNativeValue( - map['networkServiceType']), + networkServiceType: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => URLRequestNetworkServiceType.fromNativeValue( + map['networkServiceType']), + EnumMethod.value => + URLRequestNetworkServiceType.fromValue(map['networkServiceType']), + EnumMethod.name => + URLRequestNetworkServiceType.byName(map['networkServiceType']) + }, timeoutInterval: map['timeoutInterval'], url: map['url'] != null ? WebUri(map['url']) : null, ); @@ -232,21 +261,33 @@ class URLRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsCellularAccess": allowsCellularAccess, "allowsConstrainedNetworkAccess": allowsConstrainedNetworkAccess, "allowsExpensiveNetworkAccess": allowsExpensiveNetworkAccess, "assumesHTTP3Capable": assumesHTTP3Capable, - "attribution": attribution?.toNativeValue(), + "attribution": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => attribution?.toNativeValue(), + EnumMethod.value => attribution?.toValue(), + EnumMethod.name => attribution?.name() + }, "body": body, - "cachePolicy": cachePolicy?.toNativeValue(), + "cachePolicy": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => cachePolicy?.toNativeValue(), + EnumMethod.value => cachePolicy?.toValue(), + EnumMethod.name => cachePolicy?.name() + }, "headers": headers, "httpShouldHandleCookies": httpShouldHandleCookies, "httpShouldUsePipelining": httpShouldUsePipelining, "mainDocumentURL": mainDocumentURL?.toString(), "method": method, - "networkServiceType": networkServiceType?.toNativeValue(), + "networkServiceType": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => networkServiceType?.toNativeValue(), + EnumMethod.value => networkServiceType?.toValue(), + EnumMethod.name => networkServiceType?.name() + }, "timeoutInterval": timeoutInterval, "url": url?.toString(), }; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart index e40c4545f..82480202f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_attribution.g.dart @@ -60,12 +60,53 @@ class URLRequestAttribution { return null; } + /// Gets a possible [URLRequestAttribution] instance value with name [name]. + /// + /// Goes through [URLRequestAttribution.values] looking for a value with + /// name [name], as reported by [URLRequestAttribution.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestAttribution? byName(String? name) { + if (name != null) { + try { + return URLRequestAttribution.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestAttribution] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestAttribution.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'DEVELOPER'; + case 1: + return 'USER'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -74,12 +115,6 @@ class URLRequestAttribution { @override String toString() { - switch (_value) { - case 0: - return 'DEVELOPER'; - case 1: - return 'USER'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart index 8424682f8..5bfeeba2a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_cache_policy.g.dart @@ -88,20 +88,44 @@ class URLRequestCachePolicy { return null; } + /// Gets a possible [URLRequestCachePolicy] instance value with name [name]. + /// + /// Goes through [URLRequestCachePolicy.values] looking for a value with + /// name [name], as reported by [URLRequestCachePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestCachePolicy? byName(String? name) { + if (name != null) { + try { + return URLRequestCachePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestCachePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestCachePolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'RELOAD_IGNORING_LOCAL_AND_REMOTE_CACHE_DATA'; @@ -118,6 +142,17 @@ class URLRequestCachePolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants used to specify interaction with the cached responses. @@ -204,20 +239,44 @@ class IOSURLRequestCachePolicy { return null; } + /// Gets a possible [IOSURLRequestCachePolicy] instance value with name [name]. + /// + /// Goes through [IOSURLRequestCachePolicy.values] looking for a value with + /// name [name], as reported by [IOSURLRequestCachePolicy.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLRequestCachePolicy? byName(String? name) { + if (name != null) { + try { + return IOSURLRequestCachePolicy.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLRequestCachePolicy] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLRequestCachePolicy.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 4: return 'RELOAD_IGNORING_LOCAL_AND_REMOTE_CACHE_DATA'; @@ -234,4 +293,15 @@ class IOSURLRequestCachePolicy { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart index 7e9d6d3dc..2c0ee1e49 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_request_network_service_type.g.dart @@ -85,20 +85,45 @@ class URLRequestNetworkServiceType { return null; } + /// Gets a possible [URLRequestNetworkServiceType] instance value with name [name]. + /// + /// Goes through [URLRequestNetworkServiceType.values] looking for a value with + /// name [name], as reported by [URLRequestNetworkServiceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static URLRequestNetworkServiceType? byName(String? name) { + if (name != null) { + try { + return URLRequestNetworkServiceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [URLRequestNetworkServiceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in URLRequestNetworkServiceType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 8: return 'AV_STREAMING'; @@ -119,6 +144,17 @@ class URLRequestNetworkServiceType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An iOS-specific Class that represents the constants that specify how a request uses network resources. @@ -205,20 +241,45 @@ class IOSURLRequestNetworkServiceType { return null; } + /// Gets a possible [IOSURLRequestNetworkServiceType] instance value with name [name]. + /// + /// Goes through [IOSURLRequestNetworkServiceType.values] looking for a value with + /// name [name], as reported by [IOSURLRequestNetworkServiceType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSURLRequestNetworkServiceType? byName(String? name) { + if (name != null) { + try { + return IOSURLRequestNetworkServiceType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSURLRequestNetworkServiceType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSURLRequestNetworkServiceType.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 8: return 'AV_STREAMING'; @@ -239,4 +300,15 @@ class IOSURLRequestNetworkServiceType { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart index 5896d0e93..72316d7bb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_response.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'url_response.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart index afce902ab..5a59dba61 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/url_response.g.dart @@ -38,7 +38,8 @@ class URLResponse { this.url}); ///Gets a possible [URLResponse] instance from a [Map] value. - static URLResponse? fromMap(Map? map) { + static URLResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -55,7 +56,7 @@ class URLResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "expectedContentLength": expectedContentLength, "headers": headers, @@ -111,7 +112,8 @@ class IOSURLResponse { this.url}); ///Gets a possible [IOSURLResponse] instance from a [Map] value. - static IOSURLResponse? fromMap(Map? map) { + static IOSURLResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -128,7 +130,7 @@ class IOSURLResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "expectedContentLength": expectedContentLength, "headers": headers, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart index 38f1ec5ba..eadb1d2f4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_preferred_content_mode.g.dart @@ -58,20 +58,44 @@ class UserPreferredContentMode { return null; } + /// Gets a possible [UserPreferredContentMode] instance value with name [name]. + /// + /// Goes through [UserPreferredContentMode.values] looking for a value with + /// name [name], as reported by [UserPreferredContentMode.name]. + /// Returns the first value with the given name, otherwise `null`. + static UserPreferredContentMode? byName(String? name) { + if (name != null) { + try { + return UserPreferredContentMode.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UserPreferredContentMode] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in UserPreferredContentMode.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 2: return 'DESKTOP'; @@ -82,4 +106,15 @@ class UserPreferredContentMode { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart index cae6b382e..7320e6a15 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script.dart @@ -1,8 +1,9 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; import 'user_script_injection_time.dart'; import 'content_world.dart'; -import '../platform_webview_feature.dart'; +import '../in_app_webview/platform_inappwebview_controller.dart'; part 'user_script.g.dart'; @@ -23,19 +24,38 @@ class UserScript_ { bool? iosForMainFrameOnly; ///A Boolean value that indicates whether to inject the script into the main frame. - ///Specify true to inject the script only into the main frame, or false to inject it into all frames. + ///Specify `true` to inject the script only into the main frame, or false to inject it into all frames. ///The default value is `true`. - /// - ///**NOTE**: available only on iOS and MacOS. + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) bool forMainFrameOnly; ///A set of matching rules for the allowed origins. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. + /// + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. /// - ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + @SupportedPlatforms(platforms: [ + AndroidPlatform(), + IOSPlatform(), + MacOSPlatform(), + WindowsPlatform(), + ]) late Set allowedOriginRules; ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. + /// + ///**NOTE for Android**: because of how a Content World is implemented on Android, if [forMainFrameOnly] is `true`, + ///the [source] inside a specific Content World that is not [ContentWorld.PAGE] will not be executed. + ///See [ContentWorld] for more details. late ContentWorld contentWorld; @ExchangeableObjectConstructor() diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart index 99f114be0..9eb75b082 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script.g.dart @@ -9,19 +9,38 @@ part of 'user_script.dart'; ///Class that represents a script that the `WebView` injects into the web page. class UserScript { ///A set of matching rules for the allowed origins. + ///Adding `'*'` as an allowed origin or setting this to `null`, it means it will allow every origin. + ///Instead, an empty [Set] will block every origin. /// - ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + ///**NOTE for Android**: each origin pattern MUST follow the table rule of [PlatformInAppWebViewController.addWebMessageListener]. + /// + ///**NOTE for iOS, macOS, Windows**: each origin pattern will be used as a + ///Regular Expression Pattern that will be used on JavaScript side using [RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows late Set allowedOriginRules; ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. + /// + ///**NOTE for Android**: because of how a Content World is implemented on Android, if [forMainFrameOnly] is `true`, + ///the [source] inside a specific Content World that is not [ContentWorld.PAGE] will not be executed. + ///See [ContentWorld] for more details. late ContentWorld contentWorld; ///A Boolean value that indicates whether to inject the script into the main frame. - ///Specify true to inject the script only into the main frame, or false to inject it into all frames. + ///Specify `true` to inject the script only into the main frame, or false to inject it into all frames. ///The default value is `true`. /// - ///**NOTE**: available only on iOS and MacOS. + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + ///- Windows bool forMainFrameOnly; ///The script’s group name. @@ -53,32 +72,50 @@ class UserScript { } ///Gets a possible [UserScript] instance from a [Map] value. - static UserScript? fromMap(Map? map) { + static UserScript? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = UserScript( groupName: map['groupName'], - injectionTime: - UserScriptInjectionTime.fromNativeValue(map['injectionTime'])!, + injectionTime: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + UserScriptInjectionTime.fromNativeValue(map['injectionTime']), + EnumMethod.value => + UserScriptInjectionTime.fromValue(map['injectionTime']), + EnumMethod.name => UserScriptInjectionTime.byName(map['injectionTime']) + }!, iosForMainFrameOnly: map['forMainFrameOnly'], source: map['source'], ); - instance.allowedOriginRules = - Set.from(map['allowedOriginRules']!.cast()); - instance.contentWorld = map['contentWorld']; - instance.forMainFrameOnly = map['forMainFrameOnly']; + if (map['allowedOriginRules'] != null) { + instance.allowedOriginRules = + Set.from(map['allowedOriginRules']!.cast()); + } + if (map['contentWorld'] != null) { + instance.contentWorld = ContentWorld.fromMap( + map['contentWorld']?.cast(), + enumMethod: enumMethod)!; + } + if (map['forMainFrameOnly'] != null) { + instance.forMainFrameOnly = map['forMainFrameOnly']; + } return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowedOriginRules": allowedOriginRules.toList(), - "contentWorld": contentWorld.toMap(), + "contentWorld": contentWorld.toMap(enumMethod: enumMethod), "forMainFrameOnly": forMainFrameOnly, "groupName": groupName, - "injectionTime": injectionTime.toNativeValue(), + "injectionTime": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => injectionTime.toNativeValue(), + EnumMethod.value => injectionTime.toValue(), + EnumMethod.name => injectionTime.name() + }, "source": source, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart index 1dd328d73..d7b270043 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/user_script_injection_time.g.dart @@ -58,12 +58,53 @@ class UserScriptInjectionTime { return null; } + /// Gets a possible [UserScriptInjectionTime] instance value with name [name]. + /// + /// Goes through [UserScriptInjectionTime.values] looking for a value with + /// name [name], as reported by [UserScriptInjectionTime.name]. + /// Returns the first value with the given name, otherwise `null`. + static UserScriptInjectionTime? byName(String? name) { + if (name != null) { + try { + return UserScriptInjectionTime.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [UserScriptInjectionTime] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in UserScriptInjectionTime.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'AT_DOCUMENT_END'; + case 0: + return 'AT_DOCUMENT_START'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -72,12 +113,6 @@ class UserScriptInjectionTime { @override String toString() { - switch (_value) { - case 1: - return 'AT_DOCUMENT_END'; - case 0: - return 'AT_DOCUMENT_START'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart index c0c9ff89f..655090c24 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/vertical_scrollbar_position.g.dart @@ -61,20 +61,45 @@ class VerticalScrollbarPosition { return null; } + /// Gets a possible [VerticalScrollbarPosition] instance value with name [name]. + /// + /// Goes through [VerticalScrollbarPosition.values] looking for a value with + /// name [name], as reported by [VerticalScrollbarPosition.name]. + /// Returns the first value with the given name, otherwise `null`. + static VerticalScrollbarPosition? byName(String? name) { + if (name != null) { + try { + return VerticalScrollbarPosition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [VerticalScrollbarPosition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in VerticalScrollbarPosition.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'SCROLLBAR_POSITION_DEFAULT'; @@ -85,6 +110,17 @@ class VerticalScrollbarPosition { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } ///An Android-specific class used to configure the position of the vertical scroll bar. @@ -145,20 +181,45 @@ class AndroidVerticalScrollbarPosition { return null; } + /// Gets a possible [AndroidVerticalScrollbarPosition] instance value with name [name]. + /// + /// Goes through [AndroidVerticalScrollbarPosition.values] looking for a value with + /// name [name], as reported by [AndroidVerticalScrollbarPosition.name]. + /// Returns the first value with the given name, otherwise `null`. + static AndroidVerticalScrollbarPosition? byName(String? name) { + if (name != null) { + try { + return AndroidVerticalScrollbarPosition.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [AndroidVerticalScrollbarPosition] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in AndroidVerticalScrollbarPosition.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'SCROLLBAR_POSITION_DEFAULT'; @@ -169,4 +230,15 @@ class AndroidVerticalScrollbarPosition { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart index 661b12ecb..28d47d072 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_archive_format.g.dart @@ -55,12 +55,53 @@ class WebArchiveFormat { return null; } + /// Gets a possible [WebArchiveFormat] instance value with name [name]. + /// + /// Goes through [WebArchiveFormat.values] looking for a value with + /// name [name], as reported by [WebArchiveFormat.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebArchiveFormat? byName(String? name) { + if (name != null) { + try { + return WebArchiveFormat.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebArchiveFormat] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebArchiveFormat.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'mht': + return 'MHT'; + case 'webarchive': + return 'WEBARCHIVE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart index a26174420..268c9f0a0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_authentication_session_error.g.dart @@ -60,20 +60,45 @@ class WebAuthenticationSessionError { return null; } + /// Gets a possible [WebAuthenticationSessionError] instance value with name [name]. + /// + /// Goes through [WebAuthenticationSessionError.values] looking for a value with + /// name [name], as reported by [WebAuthenticationSessionError.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebAuthenticationSessionError? byName(String? name) { + if (name != null) { + try { + return WebAuthenticationSessionError.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebAuthenticationSessionError] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebAuthenticationSessionError.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 1: return 'CANCELED_LOGIN'; @@ -84,4 +109,15 @@ class WebAuthenticationSessionError { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart index 437ab0205..aefe74dc9 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'web_history_item.dart'; +import 'enum_method.dart'; part 'web_history.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart index 8fc9c5f28..3feae8759 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history.g.dart @@ -16,25 +16,27 @@ class WebHistory { WebHistory({this.currentIndex, this.list}); ///Gets a possible [WebHistory] instance from a [Map] value. - static WebHistory? fromMap(Map? map) { + static WebHistory? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebHistory( currentIndex: map['currentIndex'], list: map['list'] != null - ? List.from(map['list'] - .map((e) => WebHistoryItem.fromMap(e?.cast())!)) + ? List.from(map['list'].map((e) => + WebHistoryItem.fromMap(e?.cast(), + enumMethod: enumMethod)!)) : null, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "currentIndex": currentIndex, - "list": list?.map((e) => e.toMap()).toList(), + "list": list?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart index 657b8837e..c09e9cb48 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.dart @@ -2,6 +2,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../web_uri.dart'; import 'web_history.dart'; +import 'enum_method.dart'; part 'web_history_item.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart index bcffea28a..698c0503f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_history_item.g.dart @@ -38,7 +38,8 @@ class WebHistoryItem { this.url}); ///Gets a possible [WebHistoryItem] instance from a [Map] value. - static WebHistoryItem? fromMap(Map? map) { + static WebHistoryItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -55,7 +56,7 @@ class WebHistoryItem { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "entryId": entryId, "index": index, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart index de20e8c11..a10b0e1eb 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'web_resource_error_type.dart'; +import 'enum_method.dart'; part 'web_resource_error.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart index 9638cf607..184c1327e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error.g.dart @@ -16,22 +16,32 @@ class WebResourceError { WebResourceError({required this.description, required this.type}); ///Gets a possible [WebResourceError] instance from a [Map] value. - static WebResourceError? fromMap(Map? map) { + static WebResourceError? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebResourceError( description: map['description'], - type: WebResourceErrorType.fromNativeValue(map['type'])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WebResourceErrorType.fromNativeValue(map['type']), + EnumMethod.value => WebResourceErrorType.fromValue(map['type']), + EnumMethod.name => WebResourceErrorType.byName(map['type']) + }!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "description": description, - "type": type.toNativeValue(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart index bbf3f6d86..f21682027 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_error_type.g.dart @@ -1239,12 +1239,171 @@ class WebResourceErrorType { return null; } + /// Gets a possible [WebResourceErrorType] instance value with name [name]. + /// + /// Goes through [WebResourceErrorType.values] looking for a value with + /// name [name], as reported by [WebResourceErrorType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebResourceErrorType? byName(String? name) { + if (name != null) { + try { + return WebResourceErrorType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebResourceErrorType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebResourceErrorType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'APP_TRANSPORT_SECURITY_REQUIRES_SECURE_CONNECTION': + return 'APP_TRANSPORT_SECURITY_REQUIRES_SECURE_CONNECTION'; + case 'BACKGROUND_SESSION_IN_USE_BY_ANOTHER_PROCESS': + return 'BACKGROUND_SESSION_IN_USE_BY_ANOTHER_PROCESS'; + case 'BACKGROUND_SESSION_REQUIRES_SHARED_CONTAINER': + return 'BACKGROUND_SESSION_REQUIRES_SHARED_CONTAINER'; + case 'BACKGROUND_SESSION_WAS_DISCONNECTED': + return 'BACKGROUND_SESSION_WAS_DISCONNECTED'; + case 'BAD_SERVER_RESPONSE': + return 'BAD_SERVER_RESPONSE'; + case 'BAD_URL': + return 'BAD_URL'; + case 'CALL_IS_ACTIVE': + return 'CALL_IS_ACTIVE'; + case 'CANCELLED': + return 'CANCELLED'; + case 'CANNOT_CLOSE_FILE': + return 'CANNOT_CLOSE_FILE'; + case 'CANNOT_CONNECT_TO_HOST': + return 'CANNOT_CONNECT_TO_HOST'; + case 'CANNOT_CREATE_FILE': + return 'CANNOT_CREATE_FILE'; + case 'CANNOT_DECODE_CONTENT_DATA': + return 'CANNOT_DECODE_CONTENT_DATA'; + case 'CANNOT_DECODE_RAW_DATA': + return 'CANNOT_DECODE_RAW_DATA'; + case 'CANNOT_LOAD_FROM_NETWORK': + return 'CANNOT_LOAD_FROM_NETWORK'; + case 'CANNOT_MOVE_FILE': + return 'CANNOT_MOVE_FILE'; + case 'CANNOT_OPEN_FILE': + return 'CANNOT_OPEN_FILE'; + case 'CANNOT_PARSE_RESPONSE': + return 'CANNOT_PARSE_RESPONSE'; + case 'CANNOT_REMOVE_FILE': + return 'CANNOT_REMOVE_FILE'; + case 'CANNOT_WRITE_TO_FILE': + return 'CANNOT_WRITE_TO_FILE'; + case 'CLIENT_CERTIFICATE_REJECTED': + return 'CLIENT_CERTIFICATE_REJECTED'; + case 'CLIENT_CERTIFICATE_REQUIRED': + return 'CLIENT_CERTIFICATE_REQUIRED'; + case 'CONNECTION_ABORTED': + return 'CONNECTION_ABORTED'; + case 'DATA_LENGTH_EXCEEDS_MAXIMUM': + return 'DATA_LENGTH_EXCEEDS_MAXIMUM'; + case 'DATA_NOT_ALLOWED': + return 'DATA_NOT_ALLOWED'; + case 'DOWNLOAD_DECODING_FAILED_MID_STREAM': + return 'DOWNLOAD_DECODING_FAILED_MID_STREAM'; + case 'DOWNLOAD_DECODING_FAILED_TO_COMPLETE': + return 'DOWNLOAD_DECODING_FAILED_TO_COMPLETE'; + case 'FAILED_SSL_HANDSHAKE': + return 'FAILED_SSL_HANDSHAKE'; + case 'FILE_IS_DIRECTORY': + return 'FILE_IS_DIRECTORY'; + case 'FILE_NOT_FOUND': + return 'FILE_NOT_FOUND'; + case 'GENERIC_FILE_ERROR': + return 'GENERIC_FILE_ERROR'; + case 'HOST_LOOKUP': + return 'HOST_LOOKUP'; + case 'INTERNATIONAL_ROAMING_OFF': + return 'INTERNATIONAL_ROAMING_OFF'; + case 'IO': + return 'IO'; + case 'NETWORK_CONNECTION_LOST': + return 'NETWORK_CONNECTION_LOST'; + case 'NOT_CONNECTED_TO_INTERNET': + return 'NOT_CONNECTED_TO_INTERNET'; + case 'NO_PERMISSIONS_TO_READ_FILE': + return 'NO_PERMISSIONS_TO_READ_FILE'; + case 'PROXY_AUTHENTICATION': + return 'PROXY_AUTHENTICATION'; + case 'REDIRECT_FAILED': + return 'REDIRECT_FAILED'; + case 'REDIRECT_TO_NON_EXISTENT_LOCATION': + return 'REDIRECT_TO_NON_EXISTENT_LOCATION'; + case 'REQUEST_BODY_STREAM_EXHAUSTED': + return 'REQUEST_BODY_STREAM_EXHAUSTED'; + case 'RESET': + return 'RESET'; + case 'RESOURCE_UNAVAILABLE': + return 'RESOURCE_UNAVAILABLE'; + case 'SECURE_CONNECTION_FAILED': + return 'SECURE_CONNECTION_FAILED'; + case 'SERVER_CERTIFICATE_HAS_BAD_DATE': + return 'SERVER_CERTIFICATE_HAS_BAD_DATE'; + case 'SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT': + return 'SERVER_CERTIFICATE_HAS_UNKNOWN_ROOT'; + case 'SERVER_CERTIFICATE_NOT_YET_VALID': + return 'SERVER_CERTIFICATE_NOT_YET_VALID'; + case 'SERVER_CERTIFICATE_UNTRUSTED': + return 'SERVER_CERTIFICATE_UNTRUSTED'; + case 'SERVER_UNREACHABLE': + return 'SERVER_UNREACHABLE'; + case 'TIMEOUT': + return 'TIMEOUT'; + case 'TOO_MANY_REDIRECTS': + return 'TOO_MANY_REDIRECTS'; + case 'TOO_MANY_REQUESTS': + return 'TOO_MANY_REQUESTS'; + case 'UNEXPECTED_ERROR': + return 'UNEXPECTED_ERROR'; + case 'UNKNOWN': + return 'UNKNOWN'; + case 'UNSAFE_RESOURCE': + return 'UNSAFE_RESOURCE'; + case 'UNSUPPORTED_AUTH_SCHEME': + return 'UNSUPPORTED_AUTH_SCHEME'; + case 'UNSUPPORTED_SCHEME': + return 'UNSUPPORTED_SCHEME'; + case 'USER_AUTHENTICATION_FAILED': + return 'USER_AUTHENTICATION_FAILED'; + case 'USER_AUTHENTICATION_REQUIRED': + return 'USER_AUTHENTICATION_REQUIRED'; + case 'USER_CANCELLED_AUTHENTICATION': + return 'USER_CANCELLED_AUTHENTICATION'; + case 'VALID_PROXY_AUTHENTICATION_REQUIRED': + return 'VALID_PROXY_AUTHENTICATION_REQUIRED'; + case 'ZERO_BYTE_RESOURCE': + return 'ZERO_BYTE_RESOURCE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart index f92abb1a9..8d626b472 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_uri.dart'; +import 'enum_method.dart'; part 'web_resource_request.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart index 1ba292dff..449de39c6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_request.g.dart @@ -47,7 +47,8 @@ class WebResourceRequest { required this.url}); ///Gets a possible [WebResourceRequest] instance from a [Map] value. - static WebResourceRequest? fromMap(Map? map) { + static WebResourceRequest? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -63,7 +64,7 @@ class WebResourceRequest { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "hasGesture": hasGesture, "headers": headers, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart index 8df388d38..680c97e0f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.dart @@ -1,7 +1,8 @@ import 'dart:typed_data'; - import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'web_resource_response.g.dart'; ///Class representing a resource response of the `WebView`. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart index 78ae7add3..9546a13e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_resource_response.g.dart @@ -42,7 +42,8 @@ class WebResourceResponse { this.statusCode}); ///Gets a possible [WebResourceResponse] instance from a [Map] value. - static WebResourceResponse? fromMap(Map? map) { + static WebResourceResponse? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -60,7 +61,7 @@ class WebResourceResponse { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "contentEncoding": contentEncoding, "contentType": contentType, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart index 2a532c7db..8e458fe40 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import '../web_storage/platform_web_storage_manager.dart'; +import 'enum_method.dart'; part 'web_storage_origin.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart index 11178c70b..74aaf01f3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_origin.g.dart @@ -20,7 +20,8 @@ class WebStorageOrigin { WebStorageOrigin({this.origin, this.quota, this.usage}); ///Gets a possible [WebStorageOrigin] instance from a [Map] value. - static WebStorageOrigin? fromMap(Map? map) { + static WebStorageOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -33,7 +34,7 @@ class WebStorageOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "origin": origin, "quota": quota, @@ -68,7 +69,8 @@ class AndroidWebStorageOrigin { AndroidWebStorageOrigin({this.origin, this.quota, this.usage}); ///Gets a possible [AndroidWebStorageOrigin] instance from a [Map] value. - static AndroidWebStorageOrigin? fromMap(Map? map) { + static AndroidWebStorageOrigin? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -81,7 +83,7 @@ class AndroidWebStorageOrigin { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "origin": origin, "quota": quota, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart index 2e53815ae..69546d999 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/web_storage_type.g.dart @@ -58,12 +58,52 @@ class WebStorageType { return null; } + /// Gets a possible [WebStorageType] instance value with name [name]. + /// + /// Goes through [WebStorageType.values] looking for a value with + /// name [name], as reported by [WebStorageType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebStorageType? byName(String? name) { + if (name != null) { + try { + return WebStorageType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebStorageType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebStorageType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'localStorage': + return 'LOCAL_STORAGE'; + case 'sessionStorage': + return 'SESSION_STORAGE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart index 90ed353c8..4495e4f90 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.dart @@ -1,6 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'website_data_type.dart'; +import 'enum_method.dart'; part 'website_data_record.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart index fb9a1f3ae..d40a3af01 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_record.g.dart @@ -16,14 +16,20 @@ class WebsiteDataRecord { WebsiteDataRecord({this.dataTypes, this.displayName}); ///Gets a possible [WebsiteDataRecord] instance from a [Map] value. - static WebsiteDataRecord? fromMap(Map? map) { + static WebsiteDataRecord? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = WebsiteDataRecord( dataTypes: map['dataTypes'] != null - ? Set.from( - map['dataTypes'].map((e) => WebsiteDataType.fromNativeValue(e)!)) + ? Set.from(map['dataTypes'] + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + WebsiteDataType.fromNativeValue(e), + EnumMethod.value => WebsiteDataType.fromValue(e), + EnumMethod.name => WebsiteDataType.byName(e) + }!)) : null, displayName: map['displayName'], ); @@ -31,9 +37,15 @@ class WebsiteDataRecord { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "dataTypes": dataTypes?.map((e) => e.toNativeValue()).toList(), + "dataTypes": dataTypes + ?.map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), "displayName": displayName, }; } @@ -64,14 +76,20 @@ class IOSWKWebsiteDataRecord { IOSWKWebsiteDataRecord({this.dataTypes, this.displayName}); ///Gets a possible [IOSWKWebsiteDataRecord] instance from a [Map] value. - static IOSWKWebsiteDataRecord? fromMap(Map? map) { + static IOSWKWebsiteDataRecord? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } final instance = IOSWKWebsiteDataRecord( dataTypes: map['dataTypes'] != null ? Set.from(map['dataTypes'] - .map((e) => IOSWKWebsiteDataType.fromNativeValue(e)!)) + .map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + IOSWKWebsiteDataType.fromNativeValue(e), + EnumMethod.value => IOSWKWebsiteDataType.fromValue(e), + EnumMethod.name => IOSWKWebsiteDataType.byName(e) + }!)) : null, displayName: map['displayName'], ); @@ -79,9 +97,15 @@ class IOSWKWebsiteDataRecord { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "dataTypes": dataTypes?.map((e) => e.toNativeValue()).toList(), + "dataTypes": dataTypes + ?.map((e) => switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => e.toNativeValue(), + EnumMethod.value => e.toValue(), + EnumMethod.name => e.name() + }) + .toList(), "displayName": displayName, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart index e10da39dd..f19bab6af 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/website_data_type.g.dart @@ -117,12 +117,70 @@ class WebsiteDataType { return null; } + /// Gets a possible [WebsiteDataType] instance value with name [name]. + /// + /// Goes through [WebsiteDataType.values] looking for a value with + /// name [name], as reported by [WebsiteDataType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebsiteDataType? byName(String? name) { + if (name != null) { + try { + return WebsiteDataType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebsiteDataType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebsiteDataType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALL'; + case 'WKWebsiteDataTypeCookies': + return 'WKWebsiteDataTypeCookies'; + case 'WKWebsiteDataTypeDiskCache': + return 'WKWebsiteDataTypeDiskCache'; + case 'WKWebsiteDataTypeFetchCache': + return 'WKWebsiteDataTypeFetchCache'; + case 'WKWebsiteDataTypeIndexedDBDatabases': + return 'WKWebsiteDataTypeIndexedDBDatabases'; + case 'WKWebsiteDataTypeLocalStorage': + return 'WKWebsiteDataTypeLocalStorage'; + case 'WKWebsiteDataTypeMemoryCache': + return 'WKWebsiteDataTypeMemoryCache'; + case 'WKWebsiteDataTypeOfflineWebApplicationCache': + return 'WKWebsiteDataTypeOfflineWebApplicationCache'; + case 'WKWebsiteDataTypeServiceWorkerRegistrations': + return 'WKWebsiteDataTypeServiceWorkerRegistrations'; + case 'WKWebsiteDataTypeSessionStorage': + return 'WKWebsiteDataTypeSessionStorage'; + case 'WKWebsiteDataTypeWebSQLDatabases': + return 'WKWebsiteDataTypeWebSQLDatabases'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -254,12 +312,71 @@ class IOSWKWebsiteDataType { return null; } + /// Gets a possible [IOSWKWebsiteDataType] instance value with name [name]. + /// + /// Goes through [IOSWKWebsiteDataType.values] looking for a value with + /// name [name], as reported by [IOSWKWebsiteDataType.name]. + /// Returns the first value with the given name, otherwise `null`. + static IOSWKWebsiteDataType? byName(String? name) { + if (name != null) { + try { + return IOSWKWebsiteDataType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [IOSWKWebsiteDataType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in IOSWKWebsiteDataType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'null': + return 'ALL'; + case 'WKWebsiteDataTypeCookies': + return 'WKWebsiteDataTypeCookies'; + case 'WKWebsiteDataTypeDiskCache': + return 'WKWebsiteDataTypeDiskCache'; + case 'WKWebsiteDataTypeFetchCache': + return 'WKWebsiteDataTypeFetchCache'; + case 'WKWebsiteDataTypeIndexedDBDatabases': + return 'WKWebsiteDataTypeIndexedDBDatabases'; + case 'WKWebsiteDataTypeLocalStorage': + return 'WKWebsiteDataTypeLocalStorage'; + case 'WKWebsiteDataTypeMemoryCache': + return 'WKWebsiteDataTypeMemoryCache'; + case 'WKWebsiteDataTypeOfflineWebApplicationCache': + return 'WKWebsiteDataTypeOfflineWebApplicationCache'; + case 'WKWebsiteDataTypeServiceWorkerRegistrations': + return 'WKWebsiteDataTypeServiceWorkerRegistrations'; + case 'WKWebsiteDataTypeSessionStorage': + return 'WKWebsiteDataTypeSessionStorage'; + case 'WKWebsiteDataTypeWebSQLDatabases': + return 'WKWebsiteDataTypeWebSQLDatabases'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart new file mode 100644 index 000000000..76e7bca5a --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.dart @@ -0,0 +1,130 @@ +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + +part 'webview_interface.g.dart'; + +///Class that represents the interfaces that a WebView can support. +@ExchangeableEnum() +class WebViewInterface_ { + // ignore: unused_field + final String _value; + const WebViewInterface_._internal(this._value); + + static const ICoreWebView2Environment = + const WebViewInterface_._internal("ICoreWebView2Environment"); + static const ICoreWebView2Environment2 = + const WebViewInterface_._internal("ICoreWebView2Environment2"); + static const ICoreWebView2Environment3 = + const WebViewInterface_._internal("ICoreWebView2Environment3"); + static const ICoreWebView2Environment4 = + const WebViewInterface_._internal("ICoreWebView2Environment4"); + static const ICoreWebView2Environment5 = + const WebViewInterface_._internal("ICoreWebView2Environment5"); + static const ICoreWebView2Environment6 = + const WebViewInterface_._internal("ICoreWebView2Environment6"); + static const ICoreWebView2Environment7 = + const WebViewInterface_._internal("ICoreWebView2Environment7"); + static const ICoreWebView2Environment8 = + const WebViewInterface_._internal("ICoreWebView2Environment8"); + static const ICoreWebView2Environment9 = + const WebViewInterface_._internal("ICoreWebView2Environment9"); + static const ICoreWebView2Environment10 = + const WebViewInterface_._internal("ICoreWebView2Environment10"); + static const ICoreWebView2Environment11 = + const WebViewInterface_._internal("ICoreWebView2Environment11"); + static const ICoreWebView2Environment12 = + const WebViewInterface_._internal("ICoreWebView2Environment12"); + static const ICoreWebView2Environment13 = + const WebViewInterface_._internal("ICoreWebView2Environment13"); + static const ICoreWebView2Environment14 = + const WebViewInterface_._internal("ICoreWebView2Environment14"); + + static const ICoreWebView2Controller = + const WebViewInterface_._internal("ICoreWebView2Controller"); + static const ICoreWebView2Controller2 = + const WebViewInterface_._internal("ICoreWebView2Controller2"); + static const ICoreWebView2Controller3 = + const WebViewInterface_._internal("ICoreWebView2Controller3"); + static const ICoreWebView2Controller4 = + const WebViewInterface_._internal("ICoreWebView2Controller4"); + + static const ICoreWebView2CompositionController = + const WebViewInterface_._internal("ICoreWebView2CompositionController"); + static const ICoreWebView2CompositionController2 = + const WebViewInterface_._internal("ICoreWebView2CompositionController2"); + static const ICoreWebView2CompositionController3 = + const WebViewInterface_._internal("ICoreWebView2CompositionController3"); + static const ICoreWebView2CompositionController4 = + const WebViewInterface_._internal("ICoreWebView2CompositionController4"); + + static const ICoreWebView2 = + const WebViewInterface_._internal("ICoreWebView2"); + static const ICoreWebView2_2 = + const WebViewInterface_._internal("ICoreWebView2_2"); + static const ICoreWebView2_3 = + const WebViewInterface_._internal("ICoreWebView2_3"); + static const ICoreWebView2_4 = + const WebViewInterface_._internal("ICoreWebView2_4"); + static const ICoreWebView2_5 = + const WebViewInterface_._internal("ICoreWebView2_5"); + static const ICoreWebView2_6 = + const WebViewInterface_._internal("ICoreWebView2_6"); + static const ICoreWebView2_7 = + const WebViewInterface_._internal("ICoreWebView2_7"); + static const ICoreWebView2_8 = + const WebViewInterface_._internal("ICoreWebView2_8"); + static const ICoreWebView2_9 = + const WebViewInterface_._internal("ICoreWebView2_9"); + static const ICoreWebView2_10 = + const WebViewInterface_._internal("ICoreWebView2_10"); + static const ICoreWebView2_11 = + const WebViewInterface_._internal("ICoreWebView2_11"); + static const ICoreWebView2_12 = + const WebViewInterface_._internal("ICoreWebView2_12"); + static const ICoreWebView2_13 = + const WebViewInterface_._internal("ICoreWebView2_13"); + static const ICoreWebView2_14 = + const WebViewInterface_._internal("ICoreWebView2_14"); + static const ICoreWebView2_15 = + const WebViewInterface_._internal("ICoreWebView2_15"); + static const ICoreWebView2_16 = + const WebViewInterface_._internal("ICoreWebView2_16"); + static const ICoreWebView2_17 = + const WebViewInterface_._internal("ICoreWebView2_17"); + static const ICoreWebView2_18 = + const WebViewInterface_._internal("ICoreWebView2_18"); + static const ICoreWebView2_19 = + const WebViewInterface_._internal("ICoreWebView2_19"); + static const ICoreWebView2_20 = + const WebViewInterface_._internal("ICoreWebView2_20"); + static const ICoreWebView2_21 = + const WebViewInterface_._internal("ICoreWebView2_21"); + static const ICoreWebView2_22 = + const WebViewInterface_._internal("ICoreWebView2_22"); + static const ICoreWebView2_23 = + const WebViewInterface_._internal("ICoreWebView2_23"); + static const ICoreWebView2_24 = + const WebViewInterface_._internal("ICoreWebView2_24"); + static const ICoreWebView2_25 = + const WebViewInterface_._internal("ICoreWebView2_25"); + static const ICoreWebView2_26 = + const WebViewInterface_._internal("ICoreWebView2_26"); + + static const ICoreWebView2Settings = + const WebViewInterface_._internal("ICoreWebView2Settings"); + static const ICoreWebView2Settings2 = + const WebViewInterface_._internal("ICoreWebView2Settings2"); + static const ICoreWebView2Settings3 = + const WebViewInterface_._internal("ICoreWebView2Settings3"); + static const ICoreWebView2Settings4 = + const WebViewInterface_._internal("ICoreWebView2Settings4"); + static const ICoreWebView2Settings5 = + const WebViewInterface_._internal("ICoreWebView2Settings5"); + static const ICoreWebView2Settings6 = + const WebViewInterface_._internal("ICoreWebView2Settings6"); + static const ICoreWebView2Settings7 = + const WebViewInterface_._internal("ICoreWebView2Settings7"); + static const ICoreWebView2Settings8 = + const WebViewInterface_._internal("ICoreWebView2Settings8"); + static const ICoreWebView2Settings9 = + const WebViewInterface_._internal("ICoreWebView2Settings9"); +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart new file mode 100644 index 000000000..a3cdfd661 --- /dev/null +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_interface.g.dart @@ -0,0 +1,391 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'webview_interface.dart'; + +// ************************************************************************** +// ExchangeableEnumGenerator +// ************************************************************************** + +///Class that represents the interfaces that a WebView can support. +class WebViewInterface { + final String _value; + final String _nativeValue; + const WebViewInterface._internal(this._value, this._nativeValue); +// ignore: unused_element + factory WebViewInterface._internalMultiPlatform( + String value, Function nativeValue) => + WebViewInterface._internal(value, nativeValue()); + static const ICoreWebView2 = + WebViewInterface._internal('ICoreWebView2', 'ICoreWebView2'); + static const ICoreWebView2CompositionController = WebViewInterface._internal( + 'ICoreWebView2CompositionController', + 'ICoreWebView2CompositionController'); + static const ICoreWebView2CompositionController2 = WebViewInterface._internal( + 'ICoreWebView2CompositionController2', + 'ICoreWebView2CompositionController2'); + static const ICoreWebView2CompositionController3 = WebViewInterface._internal( + 'ICoreWebView2CompositionController3', + 'ICoreWebView2CompositionController3'); + static const ICoreWebView2CompositionController4 = WebViewInterface._internal( + 'ICoreWebView2CompositionController4', + 'ICoreWebView2CompositionController4'); + static const ICoreWebView2Controller = WebViewInterface._internal( + 'ICoreWebView2Controller', 'ICoreWebView2Controller'); + static const ICoreWebView2Controller2 = WebViewInterface._internal( + 'ICoreWebView2Controller2', 'ICoreWebView2Controller2'); + static const ICoreWebView2Controller3 = WebViewInterface._internal( + 'ICoreWebView2Controller3', 'ICoreWebView2Controller3'); + static const ICoreWebView2Controller4 = WebViewInterface._internal( + 'ICoreWebView2Controller4', 'ICoreWebView2Controller4'); + static const ICoreWebView2Environment = WebViewInterface._internal( + 'ICoreWebView2Environment', 'ICoreWebView2Environment'); + static const ICoreWebView2Environment10 = WebViewInterface._internal( + 'ICoreWebView2Environment10', 'ICoreWebView2Environment10'); + static const ICoreWebView2Environment11 = WebViewInterface._internal( + 'ICoreWebView2Environment11', 'ICoreWebView2Environment11'); + static const ICoreWebView2Environment12 = WebViewInterface._internal( + 'ICoreWebView2Environment12', 'ICoreWebView2Environment12'); + static const ICoreWebView2Environment13 = WebViewInterface._internal( + 'ICoreWebView2Environment13', 'ICoreWebView2Environment13'); + static const ICoreWebView2Environment14 = WebViewInterface._internal( + 'ICoreWebView2Environment14', 'ICoreWebView2Environment14'); + static const ICoreWebView2Environment2 = WebViewInterface._internal( + 'ICoreWebView2Environment2', 'ICoreWebView2Environment2'); + static const ICoreWebView2Environment3 = WebViewInterface._internal( + 'ICoreWebView2Environment3', 'ICoreWebView2Environment3'); + static const ICoreWebView2Environment4 = WebViewInterface._internal( + 'ICoreWebView2Environment4', 'ICoreWebView2Environment4'); + static const ICoreWebView2Environment5 = WebViewInterface._internal( + 'ICoreWebView2Environment5', 'ICoreWebView2Environment5'); + static const ICoreWebView2Environment6 = WebViewInterface._internal( + 'ICoreWebView2Environment6', 'ICoreWebView2Environment6'); + static const ICoreWebView2Environment7 = WebViewInterface._internal( + 'ICoreWebView2Environment7', 'ICoreWebView2Environment7'); + static const ICoreWebView2Environment8 = WebViewInterface._internal( + 'ICoreWebView2Environment8', 'ICoreWebView2Environment8'); + static const ICoreWebView2Environment9 = WebViewInterface._internal( + 'ICoreWebView2Environment9', 'ICoreWebView2Environment9'); + static const ICoreWebView2Settings = WebViewInterface._internal( + 'ICoreWebView2Settings', 'ICoreWebView2Settings'); + static const ICoreWebView2Settings2 = WebViewInterface._internal( + 'ICoreWebView2Settings2', 'ICoreWebView2Settings2'); + static const ICoreWebView2Settings3 = WebViewInterface._internal( + 'ICoreWebView2Settings3', 'ICoreWebView2Settings3'); + static const ICoreWebView2Settings4 = WebViewInterface._internal( + 'ICoreWebView2Settings4', 'ICoreWebView2Settings4'); + static const ICoreWebView2Settings5 = WebViewInterface._internal( + 'ICoreWebView2Settings5', 'ICoreWebView2Settings5'); + static const ICoreWebView2Settings6 = WebViewInterface._internal( + 'ICoreWebView2Settings6', 'ICoreWebView2Settings6'); + static const ICoreWebView2Settings7 = WebViewInterface._internal( + 'ICoreWebView2Settings7', 'ICoreWebView2Settings7'); + static const ICoreWebView2Settings8 = WebViewInterface._internal( + 'ICoreWebView2Settings8', 'ICoreWebView2Settings8'); + static const ICoreWebView2Settings9 = WebViewInterface._internal( + 'ICoreWebView2Settings9', 'ICoreWebView2Settings9'); + static const ICoreWebView2_10 = + WebViewInterface._internal('ICoreWebView2_10', 'ICoreWebView2_10'); + static const ICoreWebView2_11 = + WebViewInterface._internal('ICoreWebView2_11', 'ICoreWebView2_11'); + static const ICoreWebView2_12 = + WebViewInterface._internal('ICoreWebView2_12', 'ICoreWebView2_12'); + static const ICoreWebView2_13 = + WebViewInterface._internal('ICoreWebView2_13', 'ICoreWebView2_13'); + static const ICoreWebView2_14 = + WebViewInterface._internal('ICoreWebView2_14', 'ICoreWebView2_14'); + static const ICoreWebView2_15 = + WebViewInterface._internal('ICoreWebView2_15', 'ICoreWebView2_15'); + static const ICoreWebView2_16 = + WebViewInterface._internal('ICoreWebView2_16', 'ICoreWebView2_16'); + static const ICoreWebView2_17 = + WebViewInterface._internal('ICoreWebView2_17', 'ICoreWebView2_17'); + static const ICoreWebView2_18 = + WebViewInterface._internal('ICoreWebView2_18', 'ICoreWebView2_18'); + static const ICoreWebView2_19 = + WebViewInterface._internal('ICoreWebView2_19', 'ICoreWebView2_19'); + static const ICoreWebView2_2 = + WebViewInterface._internal('ICoreWebView2_2', 'ICoreWebView2_2'); + static const ICoreWebView2_20 = + WebViewInterface._internal('ICoreWebView2_20', 'ICoreWebView2_20'); + static const ICoreWebView2_21 = + WebViewInterface._internal('ICoreWebView2_21', 'ICoreWebView2_21'); + static const ICoreWebView2_22 = + WebViewInterface._internal('ICoreWebView2_22', 'ICoreWebView2_22'); + static const ICoreWebView2_23 = + WebViewInterface._internal('ICoreWebView2_23', 'ICoreWebView2_23'); + static const ICoreWebView2_24 = + WebViewInterface._internal('ICoreWebView2_24', 'ICoreWebView2_24'); + static const ICoreWebView2_25 = + WebViewInterface._internal('ICoreWebView2_25', 'ICoreWebView2_25'); + static const ICoreWebView2_26 = + WebViewInterface._internal('ICoreWebView2_26', 'ICoreWebView2_26'); + static const ICoreWebView2_3 = + WebViewInterface._internal('ICoreWebView2_3', 'ICoreWebView2_3'); + static const ICoreWebView2_4 = + WebViewInterface._internal('ICoreWebView2_4', 'ICoreWebView2_4'); + static const ICoreWebView2_5 = + WebViewInterface._internal('ICoreWebView2_5', 'ICoreWebView2_5'); + static const ICoreWebView2_6 = + WebViewInterface._internal('ICoreWebView2_6', 'ICoreWebView2_6'); + static const ICoreWebView2_7 = + WebViewInterface._internal('ICoreWebView2_7', 'ICoreWebView2_7'); + static const ICoreWebView2_8 = + WebViewInterface._internal('ICoreWebView2_8', 'ICoreWebView2_8'); + static const ICoreWebView2_9 = + WebViewInterface._internal('ICoreWebView2_9', 'ICoreWebView2_9'); + + ///Set of all values of [WebViewInterface]. + static final Set values = [ + WebViewInterface.ICoreWebView2, + WebViewInterface.ICoreWebView2CompositionController, + WebViewInterface.ICoreWebView2CompositionController2, + WebViewInterface.ICoreWebView2CompositionController3, + WebViewInterface.ICoreWebView2CompositionController4, + WebViewInterface.ICoreWebView2Controller, + WebViewInterface.ICoreWebView2Controller2, + WebViewInterface.ICoreWebView2Controller3, + WebViewInterface.ICoreWebView2Controller4, + WebViewInterface.ICoreWebView2Environment, + WebViewInterface.ICoreWebView2Environment10, + WebViewInterface.ICoreWebView2Environment11, + WebViewInterface.ICoreWebView2Environment12, + WebViewInterface.ICoreWebView2Environment13, + WebViewInterface.ICoreWebView2Environment14, + WebViewInterface.ICoreWebView2Environment2, + WebViewInterface.ICoreWebView2Environment3, + WebViewInterface.ICoreWebView2Environment4, + WebViewInterface.ICoreWebView2Environment5, + WebViewInterface.ICoreWebView2Environment6, + WebViewInterface.ICoreWebView2Environment7, + WebViewInterface.ICoreWebView2Environment8, + WebViewInterface.ICoreWebView2Environment9, + WebViewInterface.ICoreWebView2Settings, + WebViewInterface.ICoreWebView2Settings2, + WebViewInterface.ICoreWebView2Settings3, + WebViewInterface.ICoreWebView2Settings4, + WebViewInterface.ICoreWebView2Settings5, + WebViewInterface.ICoreWebView2Settings6, + WebViewInterface.ICoreWebView2Settings7, + WebViewInterface.ICoreWebView2Settings8, + WebViewInterface.ICoreWebView2Settings9, + WebViewInterface.ICoreWebView2_10, + WebViewInterface.ICoreWebView2_11, + WebViewInterface.ICoreWebView2_12, + WebViewInterface.ICoreWebView2_13, + WebViewInterface.ICoreWebView2_14, + WebViewInterface.ICoreWebView2_15, + WebViewInterface.ICoreWebView2_16, + WebViewInterface.ICoreWebView2_17, + WebViewInterface.ICoreWebView2_18, + WebViewInterface.ICoreWebView2_19, + WebViewInterface.ICoreWebView2_2, + WebViewInterface.ICoreWebView2_20, + WebViewInterface.ICoreWebView2_21, + WebViewInterface.ICoreWebView2_22, + WebViewInterface.ICoreWebView2_23, + WebViewInterface.ICoreWebView2_24, + WebViewInterface.ICoreWebView2_25, + WebViewInterface.ICoreWebView2_26, + WebViewInterface.ICoreWebView2_3, + WebViewInterface.ICoreWebView2_4, + WebViewInterface.ICoreWebView2_5, + WebViewInterface.ICoreWebView2_6, + WebViewInterface.ICoreWebView2_7, + WebViewInterface.ICoreWebView2_8, + WebViewInterface.ICoreWebView2_9, + ].toSet(); + + ///Gets a possible [WebViewInterface] instance from [String] value. + static WebViewInterface? fromValue(String? value) { + if (value != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [WebViewInterface] instance from a native value. + static WebViewInterface? fromNativeValue(String? value) { + if (value != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + /// Gets a possible [WebViewInterface] instance value with name [name]. + /// + /// Goes through [WebViewInterface.values] looking for a value with + /// name [name], as reported by [WebViewInterface.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebViewInterface? byName(String? name) { + if (name != null) { + try { + return WebViewInterface.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebViewInterface] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebViewInterface.values) value.name(): value + }; + + ///Gets [String] value. + String toValue() => _value; + + ///Gets [String] native value. + String toNativeValue() => _nativeValue; + + ///Gets the name of the value. + String name() { + switch (_value) { + case 'ICoreWebView2': + return 'ICoreWebView2'; + case 'ICoreWebView2CompositionController': + return 'ICoreWebView2CompositionController'; + case 'ICoreWebView2CompositionController2': + return 'ICoreWebView2CompositionController2'; + case 'ICoreWebView2CompositionController3': + return 'ICoreWebView2CompositionController3'; + case 'ICoreWebView2CompositionController4': + return 'ICoreWebView2CompositionController4'; + case 'ICoreWebView2Controller': + return 'ICoreWebView2Controller'; + case 'ICoreWebView2Controller2': + return 'ICoreWebView2Controller2'; + case 'ICoreWebView2Controller3': + return 'ICoreWebView2Controller3'; + case 'ICoreWebView2Controller4': + return 'ICoreWebView2Controller4'; + case 'ICoreWebView2Environment': + return 'ICoreWebView2Environment'; + case 'ICoreWebView2Environment10': + return 'ICoreWebView2Environment10'; + case 'ICoreWebView2Environment11': + return 'ICoreWebView2Environment11'; + case 'ICoreWebView2Environment12': + return 'ICoreWebView2Environment12'; + case 'ICoreWebView2Environment13': + return 'ICoreWebView2Environment13'; + case 'ICoreWebView2Environment14': + return 'ICoreWebView2Environment14'; + case 'ICoreWebView2Environment2': + return 'ICoreWebView2Environment2'; + case 'ICoreWebView2Environment3': + return 'ICoreWebView2Environment3'; + case 'ICoreWebView2Environment4': + return 'ICoreWebView2Environment4'; + case 'ICoreWebView2Environment5': + return 'ICoreWebView2Environment5'; + case 'ICoreWebView2Environment6': + return 'ICoreWebView2Environment6'; + case 'ICoreWebView2Environment7': + return 'ICoreWebView2Environment7'; + case 'ICoreWebView2Environment8': + return 'ICoreWebView2Environment8'; + case 'ICoreWebView2Environment9': + return 'ICoreWebView2Environment9'; + case 'ICoreWebView2Settings': + return 'ICoreWebView2Settings'; + case 'ICoreWebView2Settings2': + return 'ICoreWebView2Settings2'; + case 'ICoreWebView2Settings3': + return 'ICoreWebView2Settings3'; + case 'ICoreWebView2Settings4': + return 'ICoreWebView2Settings4'; + case 'ICoreWebView2Settings5': + return 'ICoreWebView2Settings5'; + case 'ICoreWebView2Settings6': + return 'ICoreWebView2Settings6'; + case 'ICoreWebView2Settings7': + return 'ICoreWebView2Settings7'; + case 'ICoreWebView2Settings8': + return 'ICoreWebView2Settings8'; + case 'ICoreWebView2Settings9': + return 'ICoreWebView2Settings9'; + case 'ICoreWebView2_10': + return 'ICoreWebView2_10'; + case 'ICoreWebView2_11': + return 'ICoreWebView2_11'; + case 'ICoreWebView2_12': + return 'ICoreWebView2_12'; + case 'ICoreWebView2_13': + return 'ICoreWebView2_13'; + case 'ICoreWebView2_14': + return 'ICoreWebView2_14'; + case 'ICoreWebView2_15': + return 'ICoreWebView2_15'; + case 'ICoreWebView2_16': + return 'ICoreWebView2_16'; + case 'ICoreWebView2_17': + return 'ICoreWebView2_17'; + case 'ICoreWebView2_18': + return 'ICoreWebView2_18'; + case 'ICoreWebView2_19': + return 'ICoreWebView2_19'; + case 'ICoreWebView2_2': + return 'ICoreWebView2_2'; + case 'ICoreWebView2_20': + return 'ICoreWebView2_20'; + case 'ICoreWebView2_21': + return 'ICoreWebView2_21'; + case 'ICoreWebView2_22': + return 'ICoreWebView2_22'; + case 'ICoreWebView2_23': + return 'ICoreWebView2_23'; + case 'ICoreWebView2_24': + return 'ICoreWebView2_24'; + case 'ICoreWebView2_25': + return 'ICoreWebView2_25'; + case 'ICoreWebView2_26': + return 'ICoreWebView2_26'; + case 'ICoreWebView2_3': + return 'ICoreWebView2_3'; + case 'ICoreWebView2_4': + return 'ICoreWebView2_4'; + case 'ICoreWebView2_5': + return 'ICoreWebView2_5'; + case 'ICoreWebView2_6': + return 'ICoreWebView2_6'; + case 'ICoreWebView2_7': + return 'ICoreWebView2_7'; + case 'ICoreWebView2_8': + return 'ICoreWebView2_8'; + case 'ICoreWebView2_9': + return 'ICoreWebView2_9'; + } + return _value.toString(); + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return _value; + } +} diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart index ec5985d15..8940a25e3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'webview_package_info.g.dart'; ///Class that represents a `WebView` package info. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart index e1b2c3e8c..22fb70ec6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_package_info.g.dart @@ -16,7 +16,8 @@ class WebViewPackageInfo { WebViewPackageInfo({this.packageName, this.versionName}); ///Gets a possible [WebViewPackageInfo] instance from a [Map] value. - static WebViewPackageInfo? fromMap(Map? map) { + static WebViewPackageInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class WebViewPackageInfo { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "packageName": packageName, "versionName": versionName, @@ -58,7 +59,8 @@ class AndroidWebViewPackageInfo { AndroidWebViewPackageInfo({this.packageName, this.versionName}); ///Gets a possible [AndroidWebViewPackageInfo] instance from a [Map] value. - static AndroidWebViewPackageInfo? fromMap(Map? map) { + static AndroidWebViewPackageInfo? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -70,7 +72,7 @@ class AndroidWebViewPackageInfo { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "packageName": packageName, "versionName": versionName, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart index 1fca268bf..f88d02916 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.dart @@ -11,5 +11,8 @@ class WebViewRenderProcessAction_ { const WebViewRenderProcessAction_._internal(this._value); ///Cause this renderer to terminate. + @EnumSupportedPlatforms(platforms: [ + EnumAndroidPlatform(), + ]) static const TERMINATE = const WebViewRenderProcessAction_._internal(0); } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart index 15542077b..73494b545 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/webview_render_process_action.g.dart @@ -18,6 +18,9 @@ class WebViewRenderProcessAction { WebViewRenderProcessAction._internal(value, nativeValue()); ///Cause this renderer to terminate. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView static const TERMINATE = WebViewRenderProcessAction._internal(0, 0); ///Set of all values of [WebViewRenderProcessAction]. @@ -51,12 +54,52 @@ class WebViewRenderProcessAction { return null; } + /// Gets a possible [WebViewRenderProcessAction] instance value with name [name]. + /// + /// Goes through [WebViewRenderProcessAction.values] looking for a value with + /// name [name], as reported by [WebViewRenderProcessAction.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebViewRenderProcessAction? byName(String? name) { + if (name != null) { + try { + return WebViewRenderProcessAction.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebViewRenderProcessAction] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WebViewRenderProcessAction.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 0: + return 'TERMINATE'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -65,10 +108,6 @@ class WebViewRenderProcessAction { @override String toString() { - switch (_value) { - case 0: - return 'TERMINATE'; - } - return _value.toString(); + return name(); } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart index 9b72119f7..deba724d0 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_features.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import 'enum_method.dart'; + part 'window_features.g.dart'; ///Class that specifies optional attributes for the containing window when a new web view is requested. diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart index 431416053..248d7a9c3 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_features.g.dart @@ -42,7 +42,8 @@ class WindowFeatures { this.y}); ///Gets a possible [WindowFeatures] instance from a [Map] value. - static WindowFeatures? fromMap(Map? map) { + static WindowFeatures? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -60,7 +61,7 @@ class WindowFeatures { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsResizing": allowsResizing, "height": height, @@ -122,7 +123,8 @@ class IOSWKWindowFeatures { this.y}); ///Gets a possible [IOSWKWindowFeatures] instance from a [Map] value. - static IOSWKWindowFeatures? fromMap(Map? map) { + static IOSWKWindowFeatures? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -140,7 +142,7 @@ class IOSWKWindowFeatures { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "allowsResizing": allowsResizing, "height": height, diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart index cde3470c2..159f07f26 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_style_mask.g.dart @@ -216,26 +216,43 @@ class WindowStyleMask { return null; } + /// Gets a possible [WindowStyleMask] instance value with name [name]. + /// + /// Goes through [WindowStyleMask.values] looking for a value with + /// name [name], as reported by [WindowStyleMask.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowStyleMask? byName(String? name) { + if (name != null) { + try { + return WindowStyleMask.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowStyleMask] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WindowStyleMask.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - WindowStyleMask operator |(WindowStyleMask value) => - WindowStyleMask._internal( - value.toValue() | _value, - value.toNativeValue() != null && _nativeValue != null - ? value.toNativeValue()! | _nativeValue! - : _nativeValue); - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'BORDERLESS'; @@ -262,4 +279,21 @@ class WindowStyleMask { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + WindowStyleMask operator |(WindowStyleMask value) => + WindowStyleMask._internal( + value.toValue() | _value, + value.toNativeValue() != null && _nativeValue != null + ? value.toNativeValue()! | _nativeValue! + : _nativeValue); + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart index 543e4e157..c2e41fa62 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_titlebar_separator_style.g.dart @@ -110,20 +110,45 @@ class WindowTitlebarSeparatorStyle { return null; } + /// Gets a possible [WindowTitlebarSeparatorStyle] instance value with name [name]. + /// + /// Goes through [WindowTitlebarSeparatorStyle.values] looking for a value with + /// name [name], as reported by [WindowTitlebarSeparatorStyle.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowTitlebarSeparatorStyle? byName(String? name) { + if (name != null) { + try { + return WindowTitlebarSeparatorStyle.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowTitlebarSeparatorStyle] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => + { + for (final value in WindowTitlebarSeparatorStyle.values) + value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int?] native value. int? toNativeValue() => _nativeValue; - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { + ///Gets the name of the value. + String name() { switch (_value) { case 0: return 'AUTOMATIC'; @@ -136,4 +161,15 @@ class WindowTitlebarSeparatorStyle { } return _value.toString(); } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(value) => value == _value; + + @override + String toString() { + return name(); + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart index 78d757c10..e1628c9fe 100644 --- a/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/types/window_type.g.dart @@ -97,12 +97,54 @@ class WindowType { return null; } + /// Gets a possible [WindowType] instance value with name [name]. + /// + /// Goes through [WindowType.values] looking for a value with + /// name [name], as reported by [WindowType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WindowType? byName(String? name) { + if (name != null) { + try { + return WindowType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WindowType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WindowType.values) value.name(): value + }; + ///Gets [String] value. String toValue() => _value; ///Gets [String] native value. String toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 'CHILD': + return 'CHILD'; + case 'TABBED': + return 'TABBED'; + case 'WINDOW': + return 'WINDOW'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart index 7ce0505ff..226c6bd17 100755 --- a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; + import 'platform_web_authenticate_session.dart'; +import '../types/enum_method.dart'; part 'web_authenticate_session_settings.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart index e94d4745b..4b0d77613 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_authentication_session/web_authenticate_session_settings.g.dart @@ -31,7 +31,8 @@ class WebAuthenticationSessionSettings { {this.prefersEphemeralWebBrowserSession = false}); ///Gets a possible [WebAuthenticationSessionSettings] instance from a [Map] value. - static WebAuthenticationSessionSettings? fromMap(Map? map) { + static WebAuthenticationSessionSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart index 7265aa320..35210403e 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/platform_web_message_port.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../inappwebview_platform.dart'; +import '../types/enum_method.dart'; import '../types/web_message_callback.dart'; import 'web_message.dart'; @@ -103,7 +104,7 @@ abstract class IWebMessagePort { 'close is not implemented on the current platform'); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { throw UnimplementedError( 'toMap is not implemented on the current platform'); } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart index 33476b95c..df817e7e4 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../platform_webview_feature.dart'; import 'platform_web_message_port.dart'; +import '../types/enum_method.dart'; part 'web_message.g.dart'; diff --git a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart index facac8e4c..f3bf55b2a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_message/web_message.g.dart @@ -56,12 +56,52 @@ class WebMessageType { return null; } + /// Gets a possible [WebMessageType] instance value with name [name]. + /// + /// Goes through [WebMessageType.values] looking for a value with + /// name [name], as reported by [WebMessageType.name]. + /// Returns the first value with the given name, otherwise `null`. + static WebMessageType? byName(String? name) { + if (name != null) { + try { + return WebMessageType.values + .firstWhere((element) => element.name() == name); + } catch (e) { + return null; + } + } + return null; + } + + /// Creates a map from the names of [WebMessageType] values to the values. + /// + /// The collection that this method is called on is expected to have + /// values with distinct names, like the `values` list of an enum class. + /// Only one value for each name can occur in the created map, + /// so if two or more values have the same name (either being the + /// same value, or being values of different enum type), at most one of + /// them will be represented in the returned map. + static Map asNameMap() => { + for (final value in WebMessageType.values) value.name(): value + }; + ///Gets [int] value. int toValue() => _value; ///Gets [int] native value. int toNativeValue() => _nativeValue; + ///Gets the name of the value. + String name() { + switch (_value) { + case 1: + return 'ARRAY_BUFFER'; + case 0: + return 'STRING'; + } + return _value.toString(); + } + @override int get hashCode => _value.hashCode; @@ -70,13 +110,7 @@ class WebMessageType { @override String toString() { - switch (_value) { - case 1: - return 'ARRAY_BUFFER'; - case 0: - return 'STRING'; - } - return _value.toString(); + return name(); } } @@ -104,7 +138,8 @@ class WebMessage { } ///Gets a possible [WebMessage] instance from a [Map] value. - static WebMessage? fromMap(Map? map) { + static WebMessage? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -113,17 +148,25 @@ class WebMessage { ports: map['ports'] != null ? List.from(map['ports'].map((e) => e)) : null, - type: WebMessageType.fromNativeValue(map['type'])!, + type: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => WebMessageType.fromNativeValue(map['type']), + EnumMethod.value => WebMessageType.fromValue(map['type']), + EnumMethod.name => WebMessageType.byName(map['type']) + }!, ); return instance; } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "data": data, - "ports": ports?.map((e) => e.toMap()).toList(), - "type": type.toNativeValue(), + "ports": ports?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), + "type": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => type.toNativeValue(), + EnumMethod.value => type.toValue(), + EnumMethod.name => type.name() + }, }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart index 4a2e3bce1..1e1fc0957 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.dart @@ -1,5 +1,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import '../types/enum_method.dart'; + part 'web_storage_item.g.dart'; ///Class that represents a single web storage item of the JavaScript `window.sessionStorage` and `window.localStorage` objects. diff --git a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart index ec2f686b2..b6a6a9088 100644 --- a/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/web_storage/web_storage_item.g.dart @@ -16,7 +16,8 @@ class WebStorageItem { WebStorageItem({this.key, this.value}); ///Gets a possible [WebStorageItem] instance from a [Map] value. - static WebStorageItem? fromMap(Map? map) { + static WebStorageItem? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -28,7 +29,7 @@ class WebStorageItem { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "key": key, "value": value, diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart index 367886928..6e4d5e482 100644 --- a/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/platform_webview_environment.dart @@ -3,7 +3,12 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../debug_logging_settings.dart'; import '../inappwebview_platform.dart'; +import '../in_app_webview/platform_webview.dart'; +import '../types/browser_process_info.dart'; import '../types/disposable.dart'; +import '../types/browser_process_exited_detail.dart'; +import '../types/browser_process_infos_changed_detail.dart'; +import '../types/webview_interface.dart'; import 'webview_environment_settings.dart'; /// Object specifying creation parameters for creating a [PlatformWebViewEnvironment]. @@ -84,6 +89,52 @@ abstract class PlatformWebViewEnvironment extends PlatformInterface ///{@endtemplate} WebViewEnvironmentSettings? get settings => params.settings; + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.isInterfaceSupported} + ///Returns `true` if the WebView Environment supports the specified [interface], otherwise `false`. + ///Only the ones related to [WebViewInterface.ICoreWebView2Environment] are valid interfaces to check; + ///otherwise, it will always return `false`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows + ///{@endtemplate} + Future isInterfaceSupported(WebViewInterface interface) async { + throw UnimplementedError( + 'isInterfaceSupported is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + ///Returns a list of all process using same user data folder except for crashpad process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1108.44+ ([Official API - ICoreWebView2Environment8.GetProcessInfos](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment8?view=webview2-1.0.2849.39#getprocessinfos)) + ///{@endtemplate} + Future> getProcessInfos() async { + throw UnimplementedError( + 'getProcessInfos is not implemented on the current platform'); + } + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.getProcessInfos} + ///Returns the path of the folder where minidump files are written. + /// + ///Whenever a WebView2 process crashes, a crash dump file will be created in the crash dump folder. + ///The crash dump format is minidump files. + ///Please see [Minidump Files documentation](https://learn.microsoft.com/en-us/windows/win32/debug/minidump-files) for detailed information. + ///Normally when a single child process fails, a minidump will be generated and written to disk, + ///then the [PlatformWebViewCreationParams.onProcessFailed] event is raised. + ///But for unexpected crashes, a minidump file might + ///not be generated at all, despite whether [PlatformWebViewCreationParams.onProcessFailed] event is raised. + ///If there are multiple process failures at once, multiple minidump files could be generated. + ///Thus [getFailureReportFolderPath] could contain old minidump files that are + ///not associated with a specific [PlatformWebViewCreationParams.onProcessFailed] event. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1518.46+ ([Official API - ICoreWebView2Environment11.get_FailureReportFolderPath](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment11?view=webview2-1.0.2849.39#get_failurereportfolderpath)) + ///{@endtemplate} + Future getFailureReportFolderPath() async { + throw UnimplementedError( + 'getFailureReportFolderPath is not implemented on the current platform'); + } + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.create} ///Creates the [PlatformWebViewEnvironment] using [settings]. /// @@ -132,6 +183,71 @@ abstract class PlatformWebViewEnvironment extends PlatformInterface 'compareBrowserVersions is not implemented on the current platform'); } + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onNewBrowserVersionAvailable} + ///[onNewBrowserVersionAvailable] runs when a newer version of the WebView2 Runtime + ///is installed and available using WebView2. + ///To use the newer version of the browser you must create a new [PlatformWebViewEnvironment] and WebView. + ///The event only runs for new version from the same WebView2 Runtime from which the code is running. + ///When not running with installed WebView2 Runtime, no event is run. + /// + ///Because a user data folder is only able to be used by one browser process at a time, + ///if you want to use the same user data folder in the WebView using the new version of the browser, + ///you must close the environment and instance of WebView that are using the older version of the browser first. + ///Or simply prompt the user to restart the app. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows ([Official API - ICoreWebView2Environment.add_NewBrowserVersionAvailable](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.2849.39#add_newbrowserversionavailable)) + ///{@endtemplate} + void Function()? onNewBrowserVersionAvailable; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onBrowserProcessExited} + ///The [onBrowserProcessExited] event is raised when the collection of WebView2 Runtime + ///processes for the browser process of this environment terminate due to browser process failure + ///or normal shutdown (for example, when all associated WebViews are closed), + ///after all resources have been released (including the user data folder). + ///To learn about what these processes are, go to [Process model](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). + /// + ///Multiple app processes can share a browser process by creating their webviews + ///from a [PlatformWebViewEnvironment] with the same user data folder. + ///When the entire collection of WebView2Runtime processes for the browser process exit, + ///all associated [PlatformWebViewEnvironment] objects receive the [onBrowserProcessExited] event. + ///Multiple processes sharing the same browser process need to coordinate their + ///use of the shared user data folder to avoid race conditions and unnecessary waits. + ///For example, one process should not clear the user data folder at the same + ///time that another process recovers from a crash by recreating its WebView controls; + ///one process should not block waiting for the event if other app processes + ///are using the same browser process (the browser process will not exit + ///until those other processes have closed their webviews too). + /// + ///The difference between [onBrowserProcessExited] and [PlatformWebViewCreationParams.onProcessFailed] is that + ///[onBrowserProcessExited] is raised for any browser process exit + ///(expected or unexpected, after all associated processes have exited too), + ///while [PlatformWebViewCreationParams.onProcessFailed] is raised for + ///unexpected process exits of any kind (browser, render, GPU, and all other types), + ///or for main frame render process unresponsiveness. + ///To learn more about the WebView2 Process Model, go to + ///[Process model](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). + /// + ///In the case the browser process crashes, both [onBrowserProcessExited] and + ///[PlatformWebViewCreationParams.onProcessFailed] events are raised, but the order is not guaranteed. + ///These events are intended for different scenarios. + ///It is up to the app to coordinate the handlers so they do not try to perform + ///reliability recovery while also trying to move to a new WebView2 Runtime version + ///or remove the user data folder. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.992.28+ ([Official API - ICoreWebView2Environment5.add_BrowserProcessExited](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment5?view=webview2-1.0.2849.39#add_browserprocessexited)) + ///{@endtemplate} + void Function(BrowserProcessExitedDetail detail)? onBrowserProcessExited; + + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.onProcessInfosChanged} + ///Event fired with a list of all process using same user data folder except for crashpad process. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1108.44+ ([Official API - ICoreWebView2Environment8.add_ProcessInfosChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment8?view=webview2-1.0.2849.39#add_processinfoschanged)) + ///{@endtemplate} + void Function(BrowserProcessInfosChangedDetail detail)? onProcessInfosChanged; + ///{@template flutter_inappwebview_platform_interface.PlatformWebViewEnvironment.dispose} ///Disposes the WebView Environment reference. ///{@endtemplate} diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart index 244cdeeb9..c208d8781 100644 --- a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.dart @@ -2,6 +2,10 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import 'platform_webview_environment.dart'; import '../types/custom_scheme_registration.dart'; +import '../types/enum_method.dart'; +import '../types/environment_channel_search_kind.dart'; +import '../types/environment_release_channels.dart'; +import '../types/environment_scrollbar_style.dart'; part 'webview_environment_settings.g.dart'; @@ -104,19 +108,179 @@ class WebViewEnvironmentSettings_ { ///Set the array of custom scheme registrations to be used. @SupportedPlatforms(platforms: [ WindowsPlatform( + available: '1.0.1587.40', apiName: - 'ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations', + 'ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations', apiUrl: - 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations') + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations') ]) final List? customSchemeRegistrations; - WebViewEnvironmentSettings_( - {this.browserExecutableFolder, - this.userDataFolder, - this.additionalBrowserArguments, - this.allowSingleSignOnUsingOSPrimaryAccount, - this.language, - this.targetCompatibleBrowserVersion, - this.customSchemeRegistrations}); + ///Whether other processes can create WebView2 from WebView2Environment created + ///with the same user data folder and therefore sharing the same WebView browser process instance. + /// + ///The default value is `false`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1185.39', + apiName: + 'ICoreWebView2EnvironmentOptions2.put_ExclusiveUserDataFolderAccess', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions2?view=webview2-1.0.2849.39#put_exclusiveuserdatafolderaccess') + ]) + final bool? exclusiveUserDataFolderAccess; + + ///When IsCustomCrashReportingEnabled is set to `true`, + ///Windows won't send crash data to Microsoft endpoint. + /// + ///The default value is `false`. + ///In this case, WebView will respect OS consent. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1518.46', + apiName: + 'ICoreWebView2EnvironmentOptions3.put_IsCustomCrashReportingEnabled', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions3?view=webview2-1.0.2849.39#put_iscustomcrashreportingenabled') + ]) + final bool? isCustomCrashReportingEnabled; + + ///This property is used to enable/disable tracking prevention feature in WebView2. + /// + ///This property enable/disable tracking prevention for all the WebView2's created in the same environment. + ///By default this feature is enabled to block potentially harmful trackers and trackers from sites that aren't visited before. + /// + ///You can set this property to `false` to disable the tracking prevention feature if the app + ///only renders content in the WebView2 that is known to be safe. + ///Disabling this feature when creating environment also improves runtime performance by skipping related code. + /// + ///You shouldn't disable this property if WebView2 is being used as a "full browser" + ///with arbitrary navigation and should protect end user privacy. + /// + ///Tracking prevention protects users from online tracking by restricting the ability + ///of trackers to access browser-based storage as well as the network. + ///See [Tracking prevention](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/tracking-prevention). + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.1661.34', + apiName: + 'ICoreWebView2EnvironmentOptions5.put_EnableTrackingPrevention', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions5?view=webview2-1.0.2849.39#put_enabletrackingprevention') + ]) + final bool? enableTrackingPrevention; + + ///When this property is set to `true` new extensions can be added to user profile and used. + /// + ///[areBrowserExtensionsEnabled] is default to be `false`, in this case, new extensions can't be installed, + ///and already installed extension won't be available to use in user profile. + ///If connecting to an already running environment with a different value for + ///[areBrowserExtensionsEnabled] property, it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2210.55', + apiName: + 'ICoreWebView2EnvironmentOptions6.put_AreBrowserExtensionsEnabled', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions6?view=webview2-1.0.2849.39#put_arebrowserextensionsenabled') + ]) + final bool? areBrowserExtensionsEnabled; + + ///This property is [EnvironmentChannelSearchKind.MOST_STABLE] by default; + ///environment creation searches for a release channel on the machine from + ///most to least stable using the first channel found. + /// + ///The default search order is: WebView2 Runtime -> Beta -> Dev -> Canary. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so that environment creation searches for a channel from least to most stable. + ///If [releaseChannels] has been provided, the loader will only search for channels in the set. + ///See [EnvironmentReleaseChannels] for more details on channels. + /// + ///This property can be overridden by the corresponding registry key [channelSearchKind] + ///or the environment variable `WEBVIEW2_CHANNEL_SEARCH_KIND`. + ///Set the value to `1` to set the search kind to [EnvironmentChannelSearchKind.LEAST_STABLE]. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2478.35', + apiName: 'ICoreWebView2EnvironmentOptions7.put_ChannelSearchKind', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_channelsearchkind') + ]) + final EnvironmentChannelSearchKind_? channelSearchKind; + + ///Sets the [releaseChannels], which is a mask of one or more [EnvironmentReleaseChannels] + ///indicating which channels environment creation should search for. + /// + ///OR operation(s) can be applied to multiple [EnvironmentReleaseChannels] to create a mask. + ///The default value is a a mask of all the channels. + ///By default, environment creation searches for channels from most to least stable, + ///using the first channel found on the device. + ///When [releaseChannels] is provided, environment creation will only + ///search for the channels specified in the set. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so environment creation searches for least stable build first. + ///See [EnvironmentReleaseChannels] for descriptions of each channel. + /// + ///The [PlatformWebViewEnvironment] creation will fails with `HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)` + ///if environment creation is unable to find any channel from the EnvironmentReleaseChannels installed on the device. + ///Use [PlatformWebViewEnvironment.getAvailableVersion] to verify which channel is used when this option is set. + /// + ///Examples: + /// + ///| ReleaseChannels | Channel Search Kind: Most Stable (default) | Channel Search Kind: Least Stable | + ///|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|--------------------------------------------| + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta | Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.DEV] | [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Dev -> Canary | Canary -> Dev -> Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | Canary | Canary | + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Canary | Canary -> Beta -> WebView2 Runtime | + /// + ///If both [browserExecutableFolder] and [releaseChannels] are provided, the [browserExecutableFolder] takes precedence, + ///regardless of whether or not the channel of [browserExecutableFolder] is included in the [releaseChannels]. + ///[releaseChannels] can be overridden by the corresponding registry override EnvironmentReleaseChannels or the environment + ///variable `WEBVIEW2_RELEASE_CHANNELS`. Set the value to a comma-separated string of integers, which map to + ///the following release channel values: Stable (0), Beta (1), Dev (2), and Canary (3). + ///For example, the values "0,2" and "2,0" indicate that environment creation should only search for + ///Dev channel and the WebView2 Runtime, using the order indicated by [channelSearchKind]. + ///[PlatformWebViewEnvironment] creation attempts to interpret each integer and treats any invalid entry as Stable channel. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2478.35', + apiName: 'ICoreWebView2EnvironmentOptions7.put_ReleaseChannels', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_releasechannels') + ]) + final EnvironmentReleaseChannels_? releaseChannels; + + ///The ScrollBar style being set on the WebView2 Environment. + /// + ///The default value is [EnvironmentScrollbarStyle.DEFAULT] which specifies the default browser ScrollBar style. + ///The `color-scheme` CSS property needs to be set on the corresponding + ///page to allow ScrollBar to follow light or dark theme. + ///Please see [color-scheme](https://developer.mozilla.org/docs/Web/CSS/color-scheme#declaring_color_scheme_preferences) for how `color-scheme` can be set. + ///CSS styles that modify the ScrollBar applied on top of native ScrollBar styling that is selected with [scrollbarStyle]. + @SupportedPlatforms(platforms: [ + WindowsPlatform( + available: '1.0.2535.41', + apiName: 'ICoreWebView2EnvironmentOptions8.put_ScrollBarStyle', + apiUrl: + 'https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions8?view=webview2-1.0.2849.39#put_scrollbarstyle') + ]) + final EnvironmentScrollbarStyle_? scrollbarStyle; + + WebViewEnvironmentSettings_({ + this.browserExecutableFolder, + this.userDataFolder, + this.additionalBrowserArguments, + this.allowSingleSignOnUsingOSPrimaryAccount, + this.language, + this.targetCompatibleBrowserVersion, + this.customSchemeRegistrations, + this.exclusiveUserDataFolderAccess, + this.isCustomCrashReportingEnabled, + this.enableTrackingPrevention, + this.areBrowserExtensionsEnabled, + this.channelSearchKind, + this.releaseChannels, + this.scrollbarStyle, + }); } diff --git a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart index 322058d8b..8b192f994 100644 --- a/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart +++ b/flutter_inappwebview_platform_interface/lib/src/webview_environment/webview_environment_settings.g.dart @@ -30,6 +30,17 @@ class WebViewEnvironmentSettings { ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_AllowSingleSignOnUsingOSPrimaryAccount](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_allowsinglesignonusingosprimaryaccount)) final bool? allowSingleSignOnUsingOSPrimaryAccount; + ///When this property is set to `true` new extensions can be added to user profile and used. + /// + ///[areBrowserExtensionsEnabled] is default to be `false`, in this case, new extensions can't be installed, + ///and already installed extension won't be available to use in user profile. + ///If connecting to an already running environment with a different value for + ///[areBrowserExtensionsEnabled] property, it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2210.55+ ([Official API - ICoreWebView2EnvironmentOptions6.put_AreBrowserExtensionsEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions6?view=webview2-1.0.2849.39#put_arebrowserextensionsenabled)) + final bool? areBrowserExtensionsEnabled; + ///Use [browserExecutableFolder] to specify whether WebView2 controls use a fixed ///or installed version of the WebView2 Runtime that exists on a user machine. ///To use a fixed version of the WebView2 Runtime, pass the folder path that contains @@ -50,18 +61,126 @@ class WebViewEnvironmentSettings { ///- Windows ([Official API - CreateCoreWebView2EnvironmentWithOptions.browserExecutableFolder](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-1.0.2210.55#createcorewebview2environmentwithoptions)) final String? browserExecutableFolder; + ///This property is [EnvironmentChannelSearchKind.MOST_STABLE] by default; + ///environment creation searches for a release channel on the machine from + ///most to least stable using the first channel found. + /// + ///The default search order is: WebView2 Runtime -> Beta -> Dev -> Canary. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so that environment creation searches for a channel from least to most stable. + ///If [releaseChannels] has been provided, the loader will only search for channels in the set. + ///See [EnvironmentReleaseChannels] for more details on channels. + /// + ///This property can be overridden by the corresponding registry key [channelSearchKind] + ///or the environment variable `WEBVIEW2_CHANNEL_SEARCH_KIND`. + ///Set the value to `1` to set the search kind to [EnvironmentChannelSearchKind.LEAST_STABLE]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2478.35+ ([Official API - ICoreWebView2EnvironmentOptions7.put_ChannelSearchKind](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_channelsearchkind)) + final EnvironmentChannelSearchKind? channelSearchKind; + ///Set the array of custom scheme registrations to be used. /// ///**Officially Supported Platforms/Implementations**: - ///- Windows ([Official API - ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations)) + ///- Windows 1.0.1587.40+ ([Official API - ICoreWebView2EnvironmentOptions4.SetCustomSchemeRegistrations](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions4?view=webview2-1.0.2739.15#setcustomschemeregistrations)) final List? customSchemeRegistrations; + ///This property is used to enable/disable tracking prevention feature in WebView2. + /// + ///This property enable/disable tracking prevention for all the WebView2's created in the same environment. + ///By default this feature is enabled to block potentially harmful trackers and trackers from sites that aren't visited before. + /// + ///You can set this property to `false` to disable the tracking prevention feature if the app + ///only renders content in the WebView2 that is known to be safe. + ///Disabling this feature when creating environment also improves runtime performance by skipping related code. + /// + ///You shouldn't disable this property if WebView2 is being used as a "full browser" + ///with arbitrary navigation and should protect end user privacy. + /// + ///Tracking prevention protects users from online tracking by restricting the ability + ///of trackers to access browser-based storage as well as the network. + ///See [Tracking prevention](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/tracking-prevention). + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1661.34+ ([Official API - ICoreWebView2EnvironmentOptions5.put_EnableTrackingPrevention](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions5?view=webview2-1.0.2849.39#put_enabletrackingprevention)) + final bool? enableTrackingPrevention; + + ///Whether other processes can create WebView2 from WebView2Environment created + ///with the same user data folder and therefore sharing the same WebView browser process instance. + /// + ///The default value is `false`. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1185.39+ ([Official API - ICoreWebView2EnvironmentOptions2.put_ExclusiveUserDataFolderAccess](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions2?view=webview2-1.0.2849.39#put_exclusiveuserdatafolderaccess)) + final bool? exclusiveUserDataFolderAccess; + + ///When IsCustomCrashReportingEnabled is set to `true`, + ///Windows won't send crash data to Microsoft endpoint. + /// + ///The default value is `false`. + ///In this case, WebView will respect OS consent. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.1518.46+ ([Official API - ICoreWebView2EnvironmentOptions3.put_IsCustomCrashReportingEnabled](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions3?view=webview2-1.0.2849.39#put_iscustomcrashreportingenabled)) + final bool? isCustomCrashReportingEnabled; + ///The default display language for WebView. /// ///**Officially Supported Platforms/Implementations**: ///- Windows ([Official API - ICoreWebView2EnvironmentOptions.put_Language](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions?view=webview2-1.0.2210.55#put_language)) final String? language; + ///Sets the [releaseChannels], which is a mask of one or more [EnvironmentReleaseChannels] + ///indicating which channels environment creation should search for. + /// + ///OR operation(s) can be applied to multiple [EnvironmentReleaseChannels] to create a mask. + ///The default value is a a mask of all the channels. + ///By default, environment creation searches for channels from most to least stable, + ///using the first channel found on the device. + ///When [releaseChannels] is provided, environment creation will only + ///search for the channels specified in the set. + ///Set [channelSearchKind] to [EnvironmentChannelSearchKind.LEAST_STABLE] to reverse + ///the search order so environment creation searches for least stable build first. + ///See [EnvironmentReleaseChannels] for descriptions of each channel. + /// + ///The [PlatformWebViewEnvironment] creation will fails with `HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)` + ///if environment creation is unable to find any channel from the EnvironmentReleaseChannels installed on the device. + ///Use [PlatformWebViewEnvironment.getAvailableVersion] to verify which channel is used when this option is set. + /// + ///Examples: + /// + ///| ReleaseChannels | Channel Search Kind: Most Stable (default) | Channel Search Kind: Least Stable | + ///|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|--------------------------------------------| + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta | Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.DEV] | [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Dev -> Canary | Canary -> Dev -> Beta -> WebView2 Runtime | + ///| [EnvironmentReleaseChannels.CANARY] | Canary | Canary | + ///| [EnvironmentReleaseChannels.BETA] | [EnvironmentReleaseChannels.CANARY] | [EnvironmentReleaseChannels.STABLE] | WebView2 Runtime -> Beta -> Canary | Canary -> Beta -> WebView2 Runtime | + /// + ///If both [browserExecutableFolder] and [releaseChannels] are provided, the [browserExecutableFolder] takes precedence, + ///regardless of whether or not the channel of [browserExecutableFolder] is included in the [releaseChannels]. + ///[releaseChannels] can be overridden by the corresponding registry override EnvironmentReleaseChannels or the environment + ///variable `WEBVIEW2_RELEASE_CHANNELS`. Set the value to a comma-separated string of integers, which map to + ///the following release channel values: Stable (0), Beta (1), Dev (2), and Canary (3). + ///For example, the values "0,2" and "2,0" indicate that environment creation should only search for + ///Dev channel and the WebView2 Runtime, using the order indicated by [channelSearchKind]. + ///[PlatformWebViewEnvironment] creation attempts to interpret each integer and treats any invalid entry as Stable channel. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2478.35+ ([Official API - ICoreWebView2EnvironmentOptions7.put_ReleaseChannels](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions7?view=webview2-1.0.2849.39#put_releasechannels)) + final EnvironmentReleaseChannels? releaseChannels; + + ///The ScrollBar style being set on the WebView2 Environment. + /// + ///The default value is [EnvironmentScrollbarStyle.DEFAULT] which specifies the default browser ScrollBar style. + ///The `color-scheme` CSS property needs to be set on the corresponding + ///page to allow ScrollBar to follow light or dark theme. + ///Please see [color-scheme](https://developer.mozilla.org/docs/Web/CSS/color-scheme#declaring_color_scheme_preferences) for how `color-scheme` can be set. + ///CSS styles that modify the ScrollBar applied on top of native ScrollBar styling that is selected with [scrollbarStyle]. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Windows 1.0.2535.41+ ([Official API - ICoreWebView2EnvironmentOptions8.put_ScrollBarStyle](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environmentoptions8?view=webview2-1.0.2849.39#put_scrollbarstyle)) + final EnvironmentScrollbarStyle? scrollbarStyle; + ///Specifies the version of the WebView2 Runtime binaries required to be compatible with your app. /// ///**Officially Supported Platforms/Implementations**: @@ -93,14 +212,22 @@ class WebViewEnvironmentSettings { WebViewEnvironmentSettings( {this.additionalBrowserArguments, this.allowSingleSignOnUsingOSPrimaryAccount, + this.areBrowserExtensionsEnabled, this.browserExecutableFolder, + this.channelSearchKind, this.customSchemeRegistrations, + this.enableTrackingPrevention, + this.exclusiveUserDataFolderAccess, + this.isCustomCrashReportingEnabled, this.language, + this.releaseChannels, + this.scrollbarStyle, this.targetCompatibleBrowserVersion, this.userDataFolder}); ///Gets a possible [WebViewEnvironmentSettings] instance from a [Map] value. - static WebViewEnvironmentSettings? fromMap(Map? map) { + static WebViewEnvironmentSettings? fromMap(Map? map, + {EnumMethod? enumMethod}) { if (map == null) { return null; } @@ -108,13 +235,42 @@ class WebViewEnvironmentSettings { additionalBrowserArguments: map['additionalBrowserArguments'], allowSingleSignOnUsingOSPrimaryAccount: map['allowSingleSignOnUsingOSPrimaryAccount'], + areBrowserExtensionsEnabled: map['areBrowserExtensionsEnabled'], browserExecutableFolder: map['browserExecutableFolder'], + channelSearchKind: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => EnvironmentChannelSearchKind.fromNativeValue( + map['channelSearchKind']), + EnumMethod.value => + EnvironmentChannelSearchKind.fromValue(map['channelSearchKind']), + EnumMethod.name => + EnvironmentChannelSearchKind.byName(map['channelSearchKind']) + }, customSchemeRegistrations: map['customSchemeRegistrations'] != null ? List.from(map['customSchemeRegistrations'] .map((e) => CustomSchemeRegistration.fromMap( - e?.cast())!)) + e?.cast(), + enumMethod: enumMethod)!)) : null, + enableTrackingPrevention: map['enableTrackingPrevention'], + exclusiveUserDataFolderAccess: map['exclusiveUserDataFolderAccess'], + isCustomCrashReportingEnabled: map['isCustomCrashReportingEnabled'], language: map['language'], + releaseChannels: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + EnvironmentReleaseChannels.fromNativeValue(map['releaseChannels']), + EnumMethod.value => + EnvironmentReleaseChannels.fromValue(map['releaseChannels']), + EnumMethod.name => + EnvironmentReleaseChannels.byName(map['releaseChannels']) + }, + scrollbarStyle: switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => + EnvironmentScrollbarStyle.fromNativeValue(map['scrollbarStyle']), + EnumMethod.value => + EnvironmentScrollbarStyle.fromValue(map['scrollbarStyle']), + EnumMethod.name => + EnvironmentScrollbarStyle.byName(map['scrollbarStyle']) + }, targetCompatibleBrowserVersion: map['targetCompatibleBrowserVersion'], userDataFolder: map['userDataFolder'], ); @@ -122,15 +278,35 @@ class WebViewEnvironmentSettings { } ///Converts instance to a map. - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "additionalBrowserArguments": additionalBrowserArguments, "allowSingleSignOnUsingOSPrimaryAccount": allowSingleSignOnUsingOSPrimaryAccount, + "areBrowserExtensionsEnabled": areBrowserExtensionsEnabled, "browserExecutableFolder": browserExecutableFolder, - "customSchemeRegistrations": - customSchemeRegistrations?.map((e) => e.toMap()).toList(), + "channelSearchKind": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => channelSearchKind?.toNativeValue(), + EnumMethod.value => channelSearchKind?.toValue(), + EnumMethod.name => channelSearchKind?.name() + }, + "customSchemeRegistrations": customSchemeRegistrations + ?.map((e) => e.toMap(enumMethod: enumMethod)) + .toList(), + "enableTrackingPrevention": enableTrackingPrevention, + "exclusiveUserDataFolderAccess": exclusiveUserDataFolderAccess, + "isCustomCrashReportingEnabled": isCustomCrashReportingEnabled, "language": language, + "releaseChannels": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => releaseChannels?.toNativeValue(), + EnumMethod.value => releaseChannels?.toValue(), + EnumMethod.name => releaseChannels?.name() + }, + "scrollbarStyle": switch (enumMethod ?? EnumMethod.nativeValue) { + EnumMethod.nativeValue => scrollbarStyle?.toNativeValue(), + EnumMethod.value => scrollbarStyle?.toValue(), + EnumMethod.name => scrollbarStyle?.name() + }, "targetCompatibleBrowserVersion": targetCompatibleBrowserVersion, "userDataFolder": userDataFolder, }; @@ -149,6 +325,6 @@ class WebViewEnvironmentSettings { @override String toString() { - return 'WebViewEnvironmentSettings{additionalBrowserArguments: $additionalBrowserArguments, allowSingleSignOnUsingOSPrimaryAccount: $allowSingleSignOnUsingOSPrimaryAccount, browserExecutableFolder: $browserExecutableFolder, customSchemeRegistrations: $customSchemeRegistrations, language: $language, targetCompatibleBrowserVersion: $targetCompatibleBrowserVersion, userDataFolder: $userDataFolder}'; + return 'WebViewEnvironmentSettings{additionalBrowserArguments: $additionalBrowserArguments, allowSingleSignOnUsingOSPrimaryAccount: $allowSingleSignOnUsingOSPrimaryAccount, areBrowserExtensionsEnabled: $areBrowserExtensionsEnabled, browserExecutableFolder: $browserExecutableFolder, channelSearchKind: $channelSearchKind, customSchemeRegistrations: $customSchemeRegistrations, enableTrackingPrevention: $enableTrackingPrevention, exclusiveUserDataFolderAccess: $exclusiveUserDataFolderAccess, isCustomCrashReportingEnabled: $isCustomCrashReportingEnabled, language: $language, releaseChannels: $releaseChannels, scrollbarStyle: $scrollbarStyle, targetCompatibleBrowserVersion: $targetCompatibleBrowserVersion, userDataFolder: $userDataFolder}'; } } diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart index b0d3555f3..03d7d683a 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_certificate.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import '../types/enum_method.dart'; import 'asn1_decoder.dart'; import 'asn1_object.dart'; import 'oid.dart'; @@ -86,7 +87,7 @@ class X509Certificate { Uint8List? derDataDecoded; try { - derDataDecoded = Uint8List.fromList(utf8.encode(base64buffer)); + derDataDecoded = Uint8List.fromList(base64Decode(base64buffer)); } catch (e) {} if (derDataDecoded != null) { return derDataDecoded; @@ -402,9 +403,9 @@ class X509Certificate { return description; } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "basicConstraints": basicConstraints?.toMap(), + "basicConstraints": basicConstraints?.toMap(enumMethod: enumMethod), "subjectAlternativeNames": subjectAlternativeNames, "issuerAlternativeNames": issuerAlternativeNames, "extendedKeyUsage": extendedKeyUsage, @@ -422,12 +423,15 @@ class X509Certificate { "criticalExtensionOIDs": criticalExtensionOIDs, "nonCriticalExtensionOIDs": nonCriticalExtensionOIDs, "encoded": encoded, - "publicKey": publicKey?.toMap(), - "subjectKeyIdentifier": subjectKeyIdentifier?.toMap(), - "authorityKeyIdentifier": authorityKeyIdentifier?.toMap(), - "certificatePolicies": certificatePolicies?.toMap(), - "cRLDistributionPoints": cRLDistributionPoints?.toMap(), - "authorityInfoAccess": authorityInfoAccess?.toMap(), + "publicKey": publicKey?.toMap(enumMethod: enumMethod), + "subjectKeyIdentifier": + subjectKeyIdentifier?.toMap(enumMethod: enumMethod), + "authorityKeyIdentifier": + authorityKeyIdentifier?.toMap(enumMethod: enumMethod), + "certificatePolicies": certificatePolicies?.toMap(enumMethod: enumMethod), + "cRLDistributionPoints": + cRLDistributionPoints?.toMap(enumMethod: enumMethod), + "authorityInfoAccess": authorityInfoAccess?.toMap(enumMethod: enumMethod), }; } diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart index 8618dfde5..982b1e3c5 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_extension.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import '../types/enum_method.dart'; import 'x509_certificate.dart'; import 'asn1_object.dart'; import 'oid.dart'; @@ -157,7 +158,7 @@ class BasicConstraintExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "isCA": isCA, "pathLenConstraint": pathLenConstraint, @@ -187,7 +188,7 @@ class SubjectKeyIdentifierExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "value": value, }; @@ -209,7 +210,7 @@ class AuthorityInfoAccess { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "method": method, "location": location, @@ -251,9 +252,10 @@ class AuthorityInfoAccessExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "infoAccess": infoAccess?.map((e) => e.toMap()).toList(), + "infoAccess": + infoAccess?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -327,7 +329,7 @@ class AuthorityKeyIdentifierExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "keyIdentifier": keyIdentifier, "certificateIssuer": certificateIssuer, @@ -351,7 +353,7 @@ class CertificatePolicyQualifier { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "oid": oid, "value": value, @@ -374,10 +376,11 @@ class CertificatePolicy { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "oid": oid, - "qualifiers": qualifiers?.map((e) => e.toMap()).toList(), + "qualifiers": + qualifiers?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -436,9 +439,10 @@ class CertificatePoliciesExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { - "policies": policies?.map((e) => e.toMap()).toList(), + "policies": + policies?.map((e) => e.toMap(enumMethod: enumMethod)).toList(), }; } @@ -470,7 +474,7 @@ class CRLDistributionPointsExtension extends X509Extension { return toMap().toString(); } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "crls": crls, }; diff --git a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart index c4661e906..30de5b05f 100644 --- a/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart +++ b/flutter_inappwebview_platform_interface/lib/src/x509_certificate/x509_public_key.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import '../types/enum_method.dart'; import 'asn1_decoder.dart'; import 'asn1_der_encoder.dart'; import 'asn1_object.dart'; @@ -50,7 +51,7 @@ class X509PublicKey { return null; } - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "algOid": algOid, "algName": algName, diff --git a/flutter_inappwebview_platform_interface/pubspec.yaml b/flutter_inappwebview_platform_interface/pubspec.yaml index 16a58f358..451ca6807 100644 --- a/flutter_inappwebview_platform_interface/pubspec.yaml +++ b/flutter_inappwebview_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_platform_interface description: A common platform interface for the flutter_inappwebview plugin. -version: 1.3.0+1 +version: 1.4.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_platform_interface issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,7 +20,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_internal_annotations: ^1.1.1 + flutter_inappwebview_internal_annotations: ^1.2.0 + # path: ../dev_packages/flutter_inappwebview_internal_annotations plugin_platform_interface: ^2.1.8 dev_dependencies: diff --git a/flutter_inappwebview_web/CHANGELOG.md b/flutter_inappwebview_web/CHANGELOG.md index c02525fc0..5a2ce4d00 100644 --- a/flutter_inappwebview_web/CHANGELOG.md +++ b/flutter_inappwebview_web/CHANGELOG.md @@ -1,3 +1,17 @@ +## 1.2.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 + +## 1.2.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 + +## 1.2.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Merged "[web] support iframe role and aria-hidden attributes" [2293](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2293) (thanks to [p-mazhnik](https://github.com/p-mazhnik)) +- Fixed 'Type 'int' is not a subtype of type 'JSValue' in type cast' when compiling/running using WASM + ## 1.1.2 - Updated flutter_inappwebview_platform_interface version to ^1.3.0 diff --git a/flutter_inappwebview_web/example/pubspec.lock b/flutter_inappwebview_web/example/pubspec.lock index e6765a52a..28440f2e0 100644 --- a/flutter_inappwebview_web/example/pubspec.lock +++ b/flutter_inappwebview_web/example/pubspec.lock @@ -79,25 +79,25 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_platform_interface: dependency: transitive description: name: flutter_inappwebview_platform_interface - sha256: "6862f4e08aa8f6136762e022c9c1edafb18c1dc3beb03052f2f3f2a48605a182" + sha256: "2c99bf767900ba029d825bc6f494d30169ee83cdaa038d86e85fe70571d0a655" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0-beta.2" flutter_inappwebview_web: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.1.2" + version: "1.2.0-beta.2" flutter_lints: dependency: "direct dev" description: diff --git a/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart index 0ac6a2c89..8fb657ff8 100644 --- a/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_web/lib/src/in_app_webview/headless_in_app_webview.dart @@ -30,8 +30,10 @@ class WebPlatformHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -148,6 +150,7 @@ class WebPlatformHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -350,7 +353,8 @@ class WebPlatformHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } diff --git a/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart index 89777617f..9d9f5e18f 100755 --- a/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_web/lib/src/in_app_webview/in_app_webview.dart @@ -33,8 +33,10 @@ class WebPlatformInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -157,6 +159,7 @@ class WebPlatformInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -334,7 +337,8 @@ class WebPlatformInAppWebViewWidget extends PlatformInAppWebViewWidget { settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (_webPlatformParams.onDownloadStartRequest != null && + if ((_webPlatformParams.onDownloadStartRequest != null || + _webPlatformParams.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } diff --git a/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart b/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart index 2e9a114f6..c56832b6f 100644 --- a/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart +++ b/flutter_inappwebview_web/lib/web/in_app_web_view_web_element.dart @@ -1,19 +1,19 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:developer'; +import 'dart:js_interop'; + import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'dart:js_interop'; -import 'dart:developer'; import 'package:web/web.dart'; import 'headless_inappwebview_manager.dart'; -import 'web_platform_manager.dart'; import 'js_bridge.dart'; +import 'web_platform_manager.dart'; extension on HTMLIFrameElement { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/csp external set csp(String? value); + external String? get csp; } @@ -65,7 +65,9 @@ class InAppWebViewWebElement implements Disposable { }); jsWebView = flutterInAppWebView?.createFlutterInAppWebView( - _viewId, iframe, iframeContainer); + _viewId is int ? (_viewId as int).toJS : _viewId.toString().toJS, + iframe, + iframeContainer); } /// Handles method calls over the MethodChannel of this plugin. @@ -208,7 +210,9 @@ class InAppWebViewWebElement implements Disposable { initialFile = webView.initialFile; jsWebView = flutterInAppWebView?.createFlutterInAppWebView( - _viewId, iframe, iframeContainer); + _viewId is int ? (_viewId as int).toJS : _viewId.toString().toJS, + iframe, + iframeContainer); } } } @@ -230,6 +234,8 @@ class InAppWebViewWebElement implements Disposable { iframe.referrerPolicy; iframe.name = settings!.iframeName ?? iframe.name; iframe.csp = settings!.iframeCsp ?? iframe.csp; + iframe.role = settings!.iframeRole ?? iframe.role; + iframe.ariaHidden = settings!.iframeAriaHidden ?? iframe.ariaHidden; if (settings!.iframeSandbox != null && settings!.iframeSandbox != Sandbox.ALLOW_ALL) { @@ -470,6 +476,12 @@ class InAppWebViewWebElement implements Disposable { if (settings!.iframeCsp != newSettings.iframeCsp) { iframe.csp = newSettings.iframeCsp; } + if (settings!.iframeRole != newSettings.iframeRole) { + iframe.role = newSettings.iframeRole; + } + if (settings!.iframeAriaHidden != newSettings.iframeAriaHidden) { + iframe.ariaHidden = newSettings.iframeAriaHidden; + } if (settings!.iframeSandbox != newSettings.iframeSandbox) { var sandbox = newSettings.iframeSandbox; diff --git a/flutter_inappwebview_web/pubspec.yaml b/flutter_inappwebview_web/pubspec.yaml index d6557020c..e11dd9823 100644 --- a/flutter_inappwebview_web/pubspec.yaml +++ b/flutter_inappwebview_web/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_web description: Web implementation of the flutter_inappwebview plugin. -version: 1.1.2 +version: 1.2.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_web issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -23,7 +23,8 @@ dependencies: flutter_web_plugins: sdk: flutter web: ^1.0.0 - flutter_inappwebview_platform_interface: ^1.3.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_windows/CHANGELOG.md b/flutter_inappwebview_windows/CHANGELOG.md index a1051a195..9aefe57e4 100644 --- a/flutter_inappwebview_windows/CHANGELOG.md +++ b/flutter_inappwebview_windows/CHANGELOG.md @@ -1,3 +1,38 @@ +## 0.7.0-beta.3 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 +- Merged "windows: fix WebViewEnvironment dispose crash" [#2433](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2433) (thanks to [GooRingX](https://github.com/GooRingX)) + +## 0.7.0-beta.2 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.2 +- Updated Microsoft.Web.WebView2 SDK version from `1.0.2792.45` to `1.0.2849.39` +- Implemented `disableDefaultErrorPage`, `statusBarEnabled`, `browserAcceleratorKeysEnabled`, `generalAutofillEnabled`, `passwordAutosaveEnabled`, `isPinchZoomEnabled`, `allowsBackForwardNavigationGestures`, `hiddenPdfToolbarItems`, `reputationCheckingRequired`, `nonClientRegionSupportEnabled` properties of `InAppWebViewSettings` +- Implemented `isInterfaceSupported`, `getProcessInfos`, `getFailureReportFolderPath` WebViewEnvironment methods +- Implemented `isInterfaceSupported`, `getZoomScale` InAppWebViewController method +- Implemented `onDownloadStarting`, `onAcceleratorKeyPressed` WebView event +- Implemented `exclusiveUserDataFolderAccess`, `isCustomCrashReportingEnabled`, `enableTrackingPrevention`, `areBrowserExtensionsEnabled`, `channelSearchKind`, `releaseChannels`, `scrollbarStyle` properties of `WebViewEnvironmentSettings` +- Implemented `onNewBrowserVersionAvailable`, `onBrowserProcessExited`, `onProcessInfosChanged` WebViewEnvironment events +- Send mouse leave region event to native view +- Fixed wrong channel name when creating a `WebViewEnvironment` instance +- Fixed "[Windows] Has an overlay on the desktop when the application is minimized" [#2402](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2402) +- Fixed "[Windows] missing implementation of onPermissionRequest event will cause crash when requested by the webpage" [#2404](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2404) +- Fixed "Windows: getCookies return empty list" [#2314](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2314) + +## 0.7.0-beta.1 + +- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.1 +- Updated `scrollMultiplier` default value from 6 to 1 +- Added support for `UserScript.allowedOriginRules` and `UserScript.forMainFrameOnly` parameters +- Implemented `onReceivedHttpAuthRequest`, `onReceivedClientCertRequest`, `onReceivedServerTrustAuthRequest`, `onRenderProcessGone`, `onRenderProcessUnresponsive`, `onWebContentProcessDidTerminate`, `onProcessFailed` WebView events +- Implemented `clearSslPreferences` WebView method +- Fixed `get_optional_fl_map_value` implementation in `utils/flutter.h` +- Fixed "Error in transparentBackground handling in Windows" [#2391](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2391) + +## 0.6.0 + +- Updated code to support multiple flutter windows + ## 0.5.0+2 - Fixed `InAppWebViewController.callAsyncJavaScript` not working with JSON objects diff --git a/flutter_inappwebview_windows/example/pubspec.lock b/flutter_inappwebview_windows/example/pubspec.lock index 83bd88ac0..7366198f1 100644 --- a/flutter_inappwebview_windows/example/pubspec.lock +++ b/flutter_inappwebview_windows/example/pubspec.lock @@ -79,25 +79,24 @@ packages: dependency: transitive description: name: flutter_inappwebview_internal_annotations - sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_inappwebview_platform_interface: dependency: transitive description: - name: flutter_inappwebview_platform_interface - sha256: "6862f4e08aa8f6136762e022c9c1edafb18c1dc3beb03052f2f3f2a48605a182" - url: "https://pub.dev" - source: hosted - version: "1.3.0" + path: "../../flutter_inappwebview_platform_interface" + relative: true + source: path + version: "1.4.0-beta.3" flutter_inappwebview_windows: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.5.0" + version: "0.7.0-beta.3" flutter_lints: dependency: "direct dev" description: diff --git a/flutter_inappwebview_windows/lib/src/cookie_manager.dart b/flutter_inappwebview_windows/lib/src/cookie_manager.dart index 1da14b0b7..f77c3bf49 100644 --- a/flutter_inappwebview_windows/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_windows/lib/src/cookie_manager.dart @@ -105,6 +105,7 @@ class WindowsCookieManager extends PlatformCookieManager args.putIfAbsent('sameSite', () => sameSite?.toNativeValue()); args.putIfAbsent( 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); return await channel?.invokeMethod('setCookie', args) ?? false; } @@ -123,6 +124,7 @@ class WindowsCookieManager extends PlatformCookieManager args.putIfAbsent('url', () => url.toString()); args.putIfAbsent( 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); List cookieListMap = await channel?.invokeMethod('getCookies', args) ?? []; cookieListMap = cookieListMap.cast>(); @@ -157,6 +159,7 @@ class WindowsCookieManager extends PlatformCookieManager args.putIfAbsent('url', () => url.toString()); args.putIfAbsent( 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); List cookies = await channel?.invokeMethod('getCookies', args) ?? []; cookies = cookies.cast>(); @@ -197,6 +200,7 @@ class WindowsCookieManager extends PlatformCookieManager args.putIfAbsent('path', () => path); args.putIfAbsent( 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); return await channel?.invokeMethod('deleteCookie', args) ?? false; } @@ -216,6 +220,7 @@ class WindowsCookieManager extends PlatformCookieManager args.putIfAbsent('path', () => path); args.putIfAbsent( 'webViewEnvironmentId', () => params.webViewEnvironment?.id); + args.putIfAbsent('webViewId', () => webViewController?.id); return await channel?.invokeMethod('deleteCookies', args) ?? false; } diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart index d4594ca53..dbffdd7f3 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import '../platform_util.dart'; import '_static_channel.dart'; const Map _cursors = { @@ -55,7 +56,15 @@ enum PointerButton { none, primary, secondary, tertiary } /// Pointer Event kind // Order must match InAppWebViewPointerEventKind (see in_app_webview.h) -enum InAppWebViewPointerEventKind { activate, down, enter, leave, up, update } +enum InAppWebViewPointerEventKind { + activate, + down, + enter, + leave, + up, + update, + cancel +} /// Attempts to translate a button constant such as [kPrimaryMouseButton] /// to a [PointerButton] @@ -193,13 +202,14 @@ class CustomPlatformViewController } /// Indicates whether the specified [button] is currently down. - Future _setPointerButtonState(PointerButton button, bool isDown) async { + Future _setPointerButtonState( + InAppWebViewPointerEventKind kind, PointerButton button) async { if (_isDisposed) { return; } assert(value.isInitialized); return _methodChannel.invokeMethod('setPointerButton', - {'button': button.index, 'isDown': isDown}); + {'kind': kind.index, 'button': button.index}); } /// Sets the horizontal and vertical scroll delta. @@ -259,7 +269,8 @@ class CustomPlatformView extends StatefulWidget { _CustomPlatformViewState createState() => _CustomPlatformViewState(); } -class _CustomPlatformViewState extends State { +class _CustomPlatformViewState extends State + with PlatformUtilListener { final GlobalKey _key = GlobalKey(); final _downButtons = {}; @@ -272,10 +283,16 @@ class _CustomPlatformViewState extends State { StreamSubscription? _cursorSubscription; + late final AppLifecycleListener _listener; + + PlatformUtil _platformUtil = PlatformUtil.instance(); + @override void initState() { super.initState(); + _platformUtil.addListener(this); + _controller.initialize( onPlatformViewCreated: (id) { widget.onPlatformViewCreated?.call(id); @@ -283,6 +300,14 @@ class _CustomPlatformViewState extends State { }, arguments: widget.creationParams); + _listener = AppLifecycleListener(onStateChange: (state) { + if ([AppLifecycleState.resumed, AppLifecycleState.hidden] + .contains(state)) { + _reportSurfaceSize(); + _reportWidgetPosition(); + } + }); + // Report initial surface size and widget position WidgetsBinding.instance.addPostFrameCallback((_) { _reportSurfaceSize(); @@ -296,6 +321,12 @@ class _CustomPlatformViewState extends State { }); } + @override + void onWindowMove() { + _reportSurfaceSize(); + _reportWidgetPosition(); + } + @override Widget build(BuildContext context) { return Focus( @@ -352,7 +383,8 @@ class _CustomPlatformViewState extends State { } final button = _getButton(ev.buttons); _downButtons[ev.pointer] = button; - _controller._setPointerButtonState(button, true); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.down, button); }, onPointerUp: (ev) { _pointerKind = ev.kind; @@ -367,14 +399,16 @@ class _CustomPlatformViewState extends State { } final button = _downButtons.remove(ev.pointer); if (button != null) { - _controller._setPointerButtonState(button, false); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.up, button); } }, onPointerCancel: (ev) { _pointerKind = ev.kind; final button = _downButtons.remove(ev.pointer); if (button != null) { - _controller._setPointerButtonState(button, false); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.cancel, button); } }, onPointerMove: (ev) { @@ -402,6 +436,16 @@ class _CustomPlatformViewState extends State { }, child: MouseRegion( cursor: _cursor, + onEnter: (ev) { + final button = _getButton(ev.buttons); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.enter, button); + }, + onExit: (ev) { + final button = _getButton(ev.buttons); + _controller._setPointerButtonState( + InAppWebViewPointerEventKind.leave, button); + }, child: Texture( textureId: _controller._textureId, filterQuality: widget.filterQuality, @@ -432,8 +476,10 @@ class _CustomPlatformViewState extends State { @override void dispose() { super.dispose(); + _platformUtil.removeListener(this); _cursorSubscription?.cancel(); _controller.dispose(); _focusNode.dispose(); + _listener.dispose(); } } diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart index b46a23558..5c3034b76 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart @@ -33,8 +33,10 @@ class WindowsHeadlessInAppWebViewCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -153,6 +155,7 @@ class WindowsHeadlessInAppWebViewCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -368,13 +371,24 @@ class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } - if (params.shouldInterceptAjaxRequest != null && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + if ((params.shouldInterceptAjaxRequest != null || + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart index 0b2c515d5..d916e4a86 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart @@ -37,8 +37,10 @@ class WindowsInAppWebViewWidgetCreationParams super.shouldOverrideUrlLoading, super.onLoadResource, super.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStart, + @Deprecated('Use onDownloadStarting instead') super.onDownloadStartRequest, + super.onDownloadStarting, @Deprecated('Use onLoadResourceWithCustomScheme instead') super.onLoadResourceCustomScheme, super.onLoadResourceWithCustomScheme, @@ -163,6 +165,7 @@ class WindowsInAppWebViewWidgetCreationParams onScrollChanged: params.onScrollChanged, onDownloadStart: params.onDownloadStart, onDownloadStartRequest: params.onDownloadStartRequest, + onDownloadStarting: params.onDownloadStarting, onLoadResourceCustomScheme: params.onLoadResourceCustomScheme, onLoadResourceWithCustomScheme: params.onLoadResourceWithCustomScheme, @@ -360,15 +363,24 @@ class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget { if (params.onLoadResource != null && settings.useOnLoadResource == null) { settings.useOnLoadResource = true; } - if (params.onDownloadStartRequest != null && + if ((params.onDownloadStartRequest != null || + params.onDownloadStarting != null) && settings.useOnDownloadStart == null) { settings.useOnDownloadStart = true; } if ((params.shouldInterceptAjaxRequest != null || - params.onAjaxProgress != null || - params.onAjaxReadyStateChange != null) && - settings.useShouldInterceptAjaxRequest == null) { - settings.useShouldInterceptAjaxRequest = true; + params.onAjaxProgress != null || + params.onAjaxReadyStateChange != null)) { + if (settings.useShouldInterceptAjaxRequest == null) { + settings.useShouldInterceptAjaxRequest = true; + } + if (params.onAjaxReadyStateChange != null && + settings.useOnAjaxReadyStateChange == null) { + settings.useOnAjaxReadyStateChange = true; + } + if (params.onAjaxProgress != null && settings.useOnAjaxProgress == null) { + settings.useOnAjaxProgress = true; + } } if (params.shouldInterceptFetchRequest != null && settings.useShouldInterceptFetchRequest == null) { diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart index 8f1ab3ffd..03e5dc040 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1,39 +1,19 @@ -import 'dart:io'; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; import 'dart:developer' as developer; -import 'dart:typed_data'; -import 'dart:ui'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../web_message/main.dart'; - import '../in_app_browser/in_app_browser.dart'; +import '../print_job/main.dart'; +import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - -import 'headless_in_app_webview.dart'; import '_static_channel.dart'; - -import '../print_job/main.dart'; - -///List of forbidden names for JavaScript handlers. -// ignore: non_constant_identifier_names -final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ - "onLoadResource", - "shouldInterceptAjaxRequest", - "onAjaxReadyStateChange", - "onAjaxProgress", - "shouldInterceptFetchRequest", - "onPrintRequest", - "onWindowFocus", - "onWindowBlur", - "callAsyncJavaScript", - "evaluateJavaScriptWithContentWorld" -]); +import 'headless_in_app_webview.dart'; /// Object specifying creation parameters for creating a [WindowsInAppWebViewController]. /// @@ -66,8 +46,7 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; // List of properties to be saved and restored for keep alive feature - Map _javaScriptHandlersMap = - HashMap(); + Map _javaScriptHandlersMap = HashMap(); Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] @@ -369,11 +348,11 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onScrollChanged(x, y); } break; - case "onDownloadStartRequest": + case "onDownloadStarting": if ((webviewParams != null && - // ignore: deprecated_member_use_from_same_package (webviewParams!.onDownloadStart != null || - webviewParams!.onDownloadStartRequest != null)) || + webviewParams!.onDownloadStartRequest != null || + webviewParams!.onDownloadStarting != null)) || _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); @@ -381,20 +360,25 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController DownloadStartRequest.fromMap(arguments)!; if (webviewParams != null) { - if (webviewParams!.onDownloadStartRequest != null) + if (webviewParams!.onDownloadStarting != null) + return (await webviewParams!.onDownloadStarting!( + _controllerFromPlatform, downloadStartRequest)) + ?.toMap(); + else if (webviewParams!.onDownloadStartRequest != null) webviewParams!.onDownloadStartRequest!( _controllerFromPlatform, downloadStartRequest); else { - // ignore: deprecated_member_use_from_same_package webviewParams!.onDownloadStart!( _controllerFromPlatform, downloadStartRequest.url); } } else { - // ignore: deprecated_member_use_from_same_package _inAppBrowserEventHandler! .onDownloadStart(downloadStartRequest.url); _inAppBrowserEventHandler! .onDownloadStartRequest(downloadStartRequest); + return (await _inAppBrowserEventHandler! + .onDownloadStarting(downloadStartRequest)) + ?.toMap(); } } break; @@ -910,6 +894,16 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + HttpAuthenticationChallenge challenge = HttpAuthenticationChallenge.fromMap(arguments)!; @@ -930,6 +924,16 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + ServerTrustChallenge challenge = ServerTrustChallenge.fromMap(arguments)!; @@ -950,6 +954,24 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler != null) { Map arguments = call.arguments.cast(); + + if (arguments['protectionSpace'] is Map && + arguments['protectionSpace']['sslCertificate'] is Map && + arguments['protectionSpace']['sslCertificate']['x509Certificate'] + is String) { + arguments['protectionSpace']['sslCertificate']['x509Certificate'] = + utf8.encode(arguments['protectionSpace']['sslCertificate'] + ['x509Certificate']); + } + + arguments['mutuallyTrustedCertificates'] = + (arguments['mutuallyTrustedCertificates'] as List) + .cast>() + .map((c) { + c['x509Certificate'] = utf8.encode(c['x509Certificate']); + return c; + }).toList(); + ClientCertChallenge challenge = ClientCertChallenge.fromMap(arguments)!; @@ -1422,19 +1444,53 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController this._devToolsProtocolEventListenerMap[eventName]!.call(data); } break; + case "onProcessFailed": + if ((webviewParams != null && webviewParams!.onProcessFailed != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + final detail = ProcessFailedDetail.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onProcessFailed != null) + webviewParams!.onProcessFailed!(_controllerFromPlatform, detail); + else + _inAppBrowserEventHandler!.onProcessFailed(detail); + } + break; + case "onAcceleratorKeyPressed": + if ((webviewParams != null && + webviewParams!.onAcceleratorKeyPressed != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + final detail = AcceleratorKeyPressedDetail.fromMap(arguments)!; + + if (webviewParams != null && + webviewParams!.onAcceleratorKeyPressed != null) + webviewParams!.onAcceleratorKeyPressed!( + _controllerFromPlatform, detail); + else + _inAppBrowserEventHandler!.onAcceleratorKeyPressed(detail); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; + Map handlerDataMap = + call.arguments["data"].cast(); // decode args to json - List args = jsonDecode(call.arguments["args"]); + handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]); + final handlerData = + JavaScriptHandlerFunctionData.fromMap(handlerDataMap)!; - _debugLog(handlerName, args); + _debugLog(handlerName, handlerData); switch (handlerName) { case "onLoadResource": if ((webviewParams != null && webviewParams!.onLoadResource != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); arguments["startTime"] = arguments["startTime"] is int ? arguments["startTime"].toDouble() : arguments["startTime"]; @@ -1456,7 +1512,8 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.shouldInterceptAjaxRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1473,43 +1530,46 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController if ((webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxReadyStateChange != null) - return (await webviewParams!.onAjaxReadyStateChange!( + return jsonEncode((await webviewParams!.onAjaxReadyStateChange!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxReadyStateChange(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "onAjaxProgress": if ((webviewParams != null && webviewParams!.onAjaxProgress != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); AjaxRequest request = AjaxRequest.fromMap(arguments)!; if (webviewParams != null && webviewParams!.onAjaxProgress != null) - return (await webviewParams!.onAjaxProgress!( + return jsonEncode((await webviewParams!.onAjaxProgress!( _controllerFromPlatform, request)) - ?.toNativeValue(); + ?.toNativeValue()); else - return (await _inAppBrowserEventHandler! + return jsonEncode((await _inAppBrowserEventHandler! .onAjaxProgress(request)) - ?.toNativeValue(); + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": if ((webviewParams != null && webviewParams!.shouldInterceptFetchRequest != null) || _inAppBrowserEventHandler != null) { - Map arguments = args[0].cast(); + Map arguments = + handlerData.args[0].cast(); FetchRequest request = FetchRequest.fromMap(arguments)!; if (webviewParams != null && @@ -1535,7 +1595,7 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController _inAppBrowserEventHandler!.onWindowBlur(); return null; case "onInjectedScriptLoaded": - String id = args[0]; + String id = handlerData.args[0]; var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onLoadCallback != null) { @@ -1543,7 +1603,7 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController } return null; case "onInjectedScriptError": - String id = args[0]; + String id = handlerData.args[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; if ((webviewParams != null || _inAppBrowserEventHandler != null) && onErrorCallback != null) { @@ -1555,7 +1615,19 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); + var jsHandlerResult = null; + if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerCallback) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerCallback)(handlerData.args); + } else if (_javaScriptHandlersMap[handlerName] + is JavaScriptHandlerFunction) { + jsHandlerResult = await (_javaScriptHandlersMap[handlerName] + as JavaScriptHandlerFunction)(handlerData); + } else { + jsHandlerResult = await _javaScriptHandlersMap[handlerName]!(); + } + return jsonEncode(jsHandlerResult); } catch (error, stacktrace) { developer.log(error.toString() + '\n' + stacktrace.toString(), name: 'JavaScript Handler "$handlerName"'); @@ -2002,16 +2074,14 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController @override void addJavaScriptHandler( - {required String handlerName, - required JavaScriptHandlerCallback callback}) { - assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), + {required String handlerName, required Function callback}) { + assert(!kJavaScriptHandlerForbiddenNames.contains(handlerName), '"$handlerName" is a forbidden name!'); this._javaScriptHandlersMap[handlerName] = (callback); } @override - JavaScriptHandlerCallback? removeJavaScriptHandler( - {required String handlerName}) { + Function? removeJavaScriptHandler({required String handlerName}) { return this._javaScriptHandlersMap.remove(handlerName); } @@ -2691,6 +2761,12 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController this._devToolsProtocolEventListenerMap.remove(eventName); } + @override + Future clearSslPreferences() async { + Map args = {}; + await channel?.invokeMethod('clearSslPreferences', args); + } + @override Future pause() async { Map args = {}; @@ -2703,6 +2779,14 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController await channel?.invokeMethod('resume', args); } + @override + Future isInterfaceSupported(WebViewInterface interface) async { + Map args = {}; + args.putIfAbsent('interface', () => interface.toNativeValue()); + return await channel?.invokeMethod('isInterfaceSupported', args) ?? + false; + } + @override Future getDefaultUserAgent() async { Map args = {}; @@ -2733,6 +2817,23 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController await _staticChannel.invokeMethod('clearAllCache', args); } + @override + Future setJavaScriptBridgeName(String bridgeName) async { + assert(RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName), + 'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.'); + Map args = {}; + args.putIfAbsent('bridgeName', () => bridgeName); + await _staticChannel.invokeMethod('setJavaScriptBridgeName', args); + } + + @override + Future getJavaScriptBridgeName() async { + Map args = {}; + return await _staticChannel.invokeMethod( + 'getJavaScriptBridgeName', args) ?? + ''; + } + @override Future get tRexRunnerHtml async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html'); diff --git a/flutter_inappwebview_windows/lib/src/platform_util.dart b/flutter_inappwebview_windows/lib/src/platform_util.dart index 27e96478b..3f3b7c8fa 100644 --- a/flutter_inappwebview_windows/lib/src/platform_util.dart +++ b/flutter_inappwebview_windows/lib/src/platform_util.dart @@ -1,11 +1,21 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +abstract mixin class PlatformUtilListener { + void onWindowMove() {} + void onWindowStartMove() {} + void onWindowEndMove() {} +} + ///Platform native utilities class PlatformUtil { static PlatformUtil? _instance; static const MethodChannel _channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_platformutil'); + static final ObserverList _listeners = + ObserverList(); + PlatformUtil._(); ///Get [PlatformUtil] instance. @@ -26,39 +36,32 @@ class PlatformUtil { return _instance!; } - static Future _handleMethod(MethodCall call) async {} - - String? _cachedSystemVersion; - - ///Get current platform system version. - Future getSystemVersion() async { - if (_cachedSystemVersion != null) { - return _cachedSystemVersion!; + static Future _handleMethod(MethodCall call) async { + if (call.method == 'onEvent') { + String eventName = call.arguments['eventName']; + for (final listener in _listeners) { + switch (eventName) { + case 'onWindowMove': + listener.onWindowMove(); + break; + case 'onWindowStartMove': + listener.onWindowStartMove(); + break; + case 'onWindowEndMove': + listener.onWindowEndMove(); + break; + } + } } - Map args = {}; - _cachedSystemVersion = - await _channel.invokeMethod('getSystemVersion', args); - return _cachedSystemVersion!; } - ///Format date. - Future formatDate( - {required DateTime date, - required String format, - String locale = "en_US", - String timezone = "UTC"}) async { - Map args = {}; - args.putIfAbsent('date', () => date.millisecondsSinceEpoch); - args.putIfAbsent('format', () => format); - args.putIfAbsent('locale', () => locale); - args.putIfAbsent('timezone', () => timezone); - return await _channel.invokeMethod('formatDate', args); + /// Add a listener to the window. + void addListener(PlatformUtilListener listener) { + _listeners.add(listener); } - ///Get cookie expiration date used by Web platform. - Future getWebCookieExpirationDate({required DateTime date}) async { - Map args = {}; - args.putIfAbsent('date', () => date.millisecondsSinceEpoch); - return await _channel.invokeMethod('getWebCookieExpirationDate', args); + /// Remove a listener from the window. + void removeListener(PlatformUtilListener listener) { + _listeners.remove(listener); } } diff --git a/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart index a1da365a4..c32e48735 100644 --- a/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart +++ b/flutter_inappwebview_windows/lib/src/print_job/print_job_controller.dart @@ -12,7 +12,7 @@ class WindowsPrintJobControllerCreationParams extends PlatformPrintJobControllerCreationParams { /// Creates a new [WindowsPrintJobControllerCreationParams] instance. const WindowsPrintJobControllerCreationParams( - {required super.id, super.onComplete}); + {required super.id}); /// Creates a [WindowsPrintJobControllerCreationParams] instance based on [PlatformPrintJobControllerCreationParams]. factory WindowsPrintJobControllerCreationParams.fromPlatformPrintJobControllerCreationParams( @@ -20,7 +20,7 @@ class WindowsPrintJobControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformPrintJobControllerCreationParams params) { return WindowsPrintJobControllerCreationParams( - id: params.id, onComplete: params.onComplete); + id: params.id); } } @@ -35,7 +35,6 @@ class WindowsPrintJobController extends PlatformPrintJobController : WindowsPrintJobControllerCreationParams .fromPlatformPrintJobControllerCreationParams(params), ) { - onComplete = params.onComplete; channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_${params.id}'); handler = _handleMethod; diff --git a/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart index f75c956ea..5a910bc01 100644 --- a/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart +++ b/flutter_inappwebview_windows/lib/src/web_message/web_message_port.dart @@ -67,7 +67,7 @@ class WindowsWebMessagePort extends PlatformWebMessagePort { } @override - Map toMap() { + Map toMap({EnumMethod? enumMethod}) { return { "index": params.index, "webMessageChannelId": this._webMessageChannel.params.id diff --git a/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart index 4313b9749..482c9f332 100644 --- a/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart +++ b/flutter_inappwebview_windows/lib/src/webview_environment/webview_environment.dart @@ -61,12 +61,59 @@ class WindowsWebViewEnvironment extends PlatformWebViewEnvironment } switch (call.method) { + case 'onNewBrowserVersionAvailable': + if (onNewBrowserVersionAvailable != null) { + onNewBrowserVersionAvailable?.call(); + } + break; + case 'onBrowserProcessExited': + if (onBrowserProcessExited != null) { + Map arguments = + call.arguments.cast(); + final detail = BrowserProcessExitedDetail.fromMap(arguments)!; + onBrowserProcessExited?.call(detail); + } + break; + case 'onProcessInfosChanged': + if (onProcessInfosChanged != null) { + Map arguments = + call.arguments.cast(); + final detail = BrowserProcessInfosChangedDetail.fromMap(arguments)!; + onProcessInfosChanged?.call(detail); + } default: throw UnimplementedError("Unimplemented ${call.method} method"); } return null; } + @override + Future isInterfaceSupported(WebViewInterface interface) async { + Map args = {}; + args.putIfAbsent('interface', () => interface.toNativeValue()); + return await channel?.invokeMethod('isInterfaceSupported', args) ?? + false; + } + + @override + Future> getProcessInfos() async { + Map args = {}; + final result = + await channel?.invokeMethod>('getProcessInfos', args); + return result + ?.map((e) => BrowserProcessInfo.fromMap(e.cast())) + .whereType() + .toList() ?? + []; + } + + @override + Future getFailureReportFolderPath() async { + Map args = {}; + return await channel?.invokeMethod( + 'getFailureReportFolderPath', args); + } + @override Future create( {WebViewEnvironmentSettings? settings}) async { @@ -78,8 +125,8 @@ class WindowsWebViewEnvironment extends PlatformWebViewEnvironment args.putIfAbsent('settings', () => env.settings?.toMap()); await _staticChannel.invokeMethod('create', args); - env.channel = - MethodChannel('com.pichillilorenzo/flutter_webview_environment_$id'); + env.channel = MethodChannel( + 'com.pichillilorenzo/flutter_webview_environment_${env.id}'); env.handler = env.handleMethod; env.initMethodCallHandler(); return env; diff --git a/flutter_inappwebview_windows/pubspec.yaml b/flutter_inappwebview_windows/pubspec.yaml index ed5710fcb..0ebc13a87 100644 --- a/flutter_inappwebview_windows/pubspec.yaml +++ b/flutter_inappwebview_windows/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview_windows description: Windows implementation of the flutter_inappwebview plugin. -version: 0.5.0+2 +version: 0.7.0-beta.3 homepage: https://inappwebview.dev/ repository: https://github.com/pichillilorenzo/flutter_inappwebview/tree/master/flutter_inappwebview_windows issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues @@ -20,7 +20,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_inappwebview_platform_interface: ^1.3.0 + flutter_inappwebview_platform_interface: #^1.4.0-beta.3 + path: ../flutter_inappwebview_platform_interface dev_dependencies: flutter_test: diff --git a/flutter_inappwebview_windows/windows/CMakeLists.txt b/flutter_inappwebview_windows/windows/CMakeLists.txt index adfbbf787..666fbbe08 100644 --- a/flutter_inappwebview_windows/windows/CMakeLists.txt +++ b/flutter_inappwebview_windows/windows/CMakeLists.txt @@ -5,8 +5,9 @@ cmake_minimum_required(VERSION 3.14) set(WIL_VERSION "1.0.231216.1") -set(WEBVIEW_VERSION "1.0.2792.45") -set(NLOHMANN_JSON "3.11.2") +set(WEBVIEW_VERSION "1.0.2849.39") +set(NLOHMANN_JSON_VERSION "3.11.2") +set(CPP_WINRT_VERSION "2.0.240405.15") message(VERBOSE "CMake system version is ${CMAKE_SYSTEM_VERSION} (using SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})") @@ -27,12 +28,13 @@ if(NOT NUGET) message(NOTICE "Nuget is not installed! The flutter_inappwebview_windows plugin requires it. Check https://inappwebview.dev/docs/intro#setup-windows") endif() -add_custom_target(${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD ALL) +add_custom_target(${PROJECT_NAME}_DEPS ALL) add_custom_command( - TARGET ${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD PRE_BUILD + TARGET ${PROJECT_NAME}_DEPS PRE_BUILD COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install Microsoft.Windows.CppWinRT -Version ${CPP_WINRT_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages COMMAND ${NUGET} install Microsoft.Web.WebView2 -Version ${WEBVIEW_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages - COMMAND ${NUGET} install nlohmann.json -Version ${NLOHMANN_JSON} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages + COMMAND ${NUGET} install nlohmann.json -Version ${NLOHMANN_JSON_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages DEPENDS ${NUGET} ) @@ -41,6 +43,8 @@ list(APPEND PLUGIN_SOURCES "flutter_inappwebview_windows_plugin.cpp" "flutter_inappwebview_windows_plugin.h" "utils/log.h" + "utils/defer.h" + "utils/timer.h" "utils/strconv.h" "utils/map.h" "utils/vector.h" @@ -49,6 +53,8 @@ list(APPEND PLUGIN_SOURCES "utils/flutter.h" "utils/base64.cpp" "utils/base64.h" + "utils/uri.h" + "utils/uuid.h" "types/channel_delegate.cpp" "types/channel_delegate.h" "types/base_callback_result.h" @@ -93,6 +99,50 @@ list(APPEND PLUGIN_SOURCES "types/custom_scheme_response.h" "types/custom_scheme_registration.cpp" "types/custom_scheme_registration.h" + "types/javascript_handler_function_data.cpp" + "types/javascript_handler_function_data.h" + "types/ssl_error.cpp" + "types/ssl_error.h" + "types/url_credential.cpp" + "types/url_credential.h" + "types/url_protection_space.cpp" + "types/url_protection_space.h" + "types/url_authentication_challenge.cpp" + "types/url_authentication_challenge.h" + "types/http_authentication_challenge.cpp" + "types/http_authentication_challenge.h" + "types/http_auth_response.cpp" + "types/http_auth_response.h" + "types/client_cert_challenge.cpp" + "types/client_cert_challenge.h" + "types/client_cert_response.cpp" + "types/client_cert_response.h" + "types/server_trust_challenge.cpp" + "types/server_trust_challenge.h" + "types/server_trust_auth_response.cpp" + "types/server_trust_auth_response.h" + "types/security_origin.cpp" + "types/security_origin.h" + "types/frame_info.cpp" + "types/frame_info.h" + "types/process_failed_detail.cpp" + "types/process_failed_detail.h" + "types/render_process_gone_detail.cpp" + "types/render_process_gone_detail.h" + "types/download_start_request.cpp" + "types/download_start_request.h" + "types/download_start_response.cpp" + "types/download_start_response.h" + "types/browser_process_exited_detail.cpp" + "types/browser_process_exited_detail.h" + "types/browser_process_info.cpp" + "types/browser_process_info.h" + "types/browser_process_infos_changed_detail.cpp" + "types/browser_process_infos_changed_detail.h" + "types/physical_key_status.cpp" + "types/physical_key_status.h" + "types/accelerator_key_pressed_detail.cpp" + "types/accelerator_key_pressed_detail.h" "custom_platform_view/custom_platform_view.cc" "custom_platform_view/custom_platform_view.h" "custom_platform_view/texture_bridge.cc" @@ -107,7 +157,6 @@ list(APPEND PLUGIN_SOURCES "custom_platform_view/util/string_converter.h" "custom_platform_view/util/swizzle.h" "plugin_scripts_js/plugin_scripts_util.h" - "plugin_scripts_js/javascript_bridge_js.cpp" "plugin_scripts_js/javascript_bridge_js.h" "webview_environment/webview_environment_settings.cpp" "webview_environment/webview_environment_settings.h" @@ -143,6 +192,8 @@ list(APPEND PLUGIN_SOURCES "in_app_browser/in_app_browser_channel_delegate.h" "cookie_manager.cpp" "cookie_manager.h" + "platform_util.cpp" + "platform_util.h" ) # Define the plugin library target. Its name must not be changed (see comment @@ -184,10 +235,12 @@ endif() # IMPORTANT: The apply_standard_settings function is not used here because it # is causing the plugin to fail to compile because of the usage of /WX flag. # So, creating here a custom function to apply the standard settings without the /WX flag. +# Also, added /bigobj flag. function(FLUTTER_INAPPWEBVIEW_WINDOWS_APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_options(${TARGET} PRIVATE /bigobj) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() diff --git a/flutter_inappwebview_windows/windows/cookie_manager.cpp b/flutter_inappwebview_windows/windows/cookie_manager.cpp index 2fa0eb5d3..2c1026143 100644 --- a/flutter_inappwebview_windows/windows/cookie_manager.cpp +++ b/flutter_inappwebview_windows/windows/cookie_manager.cpp @@ -1,13 +1,14 @@ -#include #include -#include -#include -#include #include "cookie_manager.h" +#include "headless_in_app_webview/headless_in_app_webview_manager.h" +#include "in_app_browser/in_app_browser_manager.h" +#include "in_app_webview/in_app_webview_manager.h" #include "types/callbacks_complete.h" #include "utils/flutter.h" #include "utils/log.h" +#include "utils/timer.h" + namespace flutter_inappwebview_plugin { @@ -25,11 +26,29 @@ namespace flutter_inappwebview_plugin auto webViewEnvironmentId = get_optional_fl_map_value(arguments, "webViewEnvironmentId"); - auto webViewEnvironment = webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) + auto webViewEnvironment = plugin && webViewEnvironmentId.has_value() && map_contains(plugin->webViewEnvironmentManager->webViewEnvironments, webViewEnvironmentId.value()) ? plugin->webViewEnvironmentManager->webViewEnvironments.at(webViewEnvironmentId.value()).get() : nullptr; + InAppWebView* webView = nullptr; + auto webViewIdInt = get_optional_fl_map_value(arguments, "webViewId"); + auto webViewIdString = !webViewIdInt.has_value() ? get_optional_fl_map_value(arguments, "webViewId") : std::optional{}; + if (webViewIdInt.has_value() && plugin->inAppWebViewManager && map_contains(plugin->inAppWebViewManager->webViews, (uint64_t)webViewIdInt.value())) { + webView = plugin->inAppWebViewManager->webViews.at(webViewIdInt.value())->view.get(); + } + else if (webViewIdString.has_value()) { + if (plugin->inAppWebViewManager && map_contains(plugin->inAppWebViewManager->keepAliveWebViews, webViewIdString.value())) { + webView = plugin->inAppWebViewManager->keepAliveWebViews.at(webViewIdString.value())->view.get(); + } + else if (plugin->headlessInAppWebViewManager && map_contains(plugin->headlessInAppWebViewManager->webViews, webViewIdString.value())) { + webView = plugin->headlessInAppWebViewManager->webViews.at(webViewIdString.value())->webView.get(); + } + else if (plugin->inAppBrowserManager && map_contains(plugin->inAppBrowserManager->browsers, webViewIdString.value())) { + webView = plugin->inAppBrowserManager->browsers.at(webViewIdString.value())->webView.get(); + } + } + auto result_ = std::shared_ptr>(std::move(result)); - auto callback = [this, result_, methodName, arguments](WebViewEnvironment* webViewEnvironment) + auto callback = [this, result_, methodName, arguments, webView](WebViewEnvironment* webViewEnvironment) { if (!webViewEnvironment) { result_->Error("0", "Cannot obtain the WebViewEnvironment!"); @@ -37,7 +56,7 @@ namespace flutter_inappwebview_plugin } if (string_equals(methodName, "setCookie")) { - setCookie(webViewEnvironment, arguments, [result_](const bool& created) + setCookie(webViewEnvironment, webView, arguments, [result_](const bool& created) { result_->Success(created); }); @@ -45,14 +64,14 @@ namespace flutter_inappwebview_plugin else if (string_equals(methodName, "getCookie")) { auto url = get_fl_map_value(arguments, "url"); auto name = get_fl_map_value(arguments, "name"); - getCookie(webViewEnvironment, url, name, [result_](const flutter::EncodableValue& cookie) + getCookie(webViewEnvironment, webView, url, name, [result_](const flutter::EncodableValue& cookie) { result_->Success(cookie); }); } else if (string_equals(methodName, "getCookies")) { auto url = get_fl_map_value(arguments, "url"); - getCookies(webViewEnvironment, url, [result_](const flutter::EncodableList& cookies) + getCookies(webViewEnvironment, webView, url, [result_](const flutter::EncodableList& cookies) { result_->Success(cookies); }); @@ -62,7 +81,7 @@ namespace flutter_inappwebview_plugin auto name = get_fl_map_value(arguments, "name"); auto path = get_fl_map_value(arguments, "path"); auto domain = get_optional_fl_map_value(arguments, "domain"); - deleteCookie(webViewEnvironment, url, name, path, domain, [result_](const bool& deleted) + deleteCookie(webViewEnvironment, webView, url, name, path, domain, [result_](const bool& deleted) { result_->Success(deleted); }); @@ -71,7 +90,7 @@ namespace flutter_inappwebview_plugin auto url = get_fl_map_value(arguments, "url"); auto path = get_fl_map_value(arguments, "path"); auto domain = get_optional_fl_map_value(arguments, "domain"); - deleteCookies(webViewEnvironment, url, path, domain, [result_](const bool& deleted) + deleteCookies(webViewEnvironment, webView, url, path, domain, [result_](const bool& deleted) { result_->Success(deleted); }); @@ -98,7 +117,7 @@ namespace flutter_inappwebview_plugin } } - void CookieManager::setCookie(WebViewEnvironment* webViewEnvironment, const flutter::EncodableMap& map, std::function completionHandler) const + void CookieManager::setCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const flutter::EncodableMap& map, std::function completionHandler) const { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -144,22 +163,61 @@ namespace flutter_inappwebview_plugin parameters["sameSite"] = sameSite.value(); } - auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.setCookie", utf8_to_wide(parameters.dump()).c_str(), Callback( - [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) { - if (completionHandler) { - completionHandler(succeededOrLog(errorCode)); + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; } - return S_OK; - } - ).Get()); - if (failedAndLog(hr) && completionHandler) { - completionHandler(false); + auto hr = webView->CallDevToolsProtocolMethod(L"Network.setCookie", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([this, callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); } } - void CookieManager::getCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, std::function completionHandler) const + void CookieManager::getCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, std::function completionHandler) const { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -172,43 +230,82 @@ namespace flutter_inappwebview_plugin {"urls", std::vector{url}} }; - auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( - [completionHandler, name](HRESULT errorCode, LPCWSTR returnObjectAsJson) + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) { - if (succeededOrLog(errorCode)) { - nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); - auto jsonCookies = json["cookies"].get>(); - for (auto& jsonCookie : jsonCookies) { - auto cookieName = jsonCookie["name"].get(); - if (string_equals(name, cookieName)) { - completionHandler(flutter::EncodableMap{ - {"name", cookieName}, - {"value", jsonCookie["value"].get()}, - {"domain", jsonCookie["domain"].get()}, - {"path", jsonCookie["path"].get()}, - {"expiresDate", jsonCookie["expires"].get()}, - {"isHttpOnly", jsonCookie["httpOnly"].get()}, - {"isSecure", jsonCookie["secure"].get()}, - {"isSessionOnly", jsonCookie["session"].get()}, - {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} - }); - return S_OK; - } + if (!webView) { + completionHandler(make_fl_value()); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); } + return; } - if (completionHandler) { - completionHandler(make_fl_value()); + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + auto cookieName = jsonCookie["name"].get(); + if (string_equals(name, cookieName)) { + completionHandler(flutter::EncodableMap{ + {"name", cookieName}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + return S_OK; + } + } + } + if (completionHandler) { + completionHandler(make_fl_value()); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(make_fl_value()); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } } - return S_OK; - } - ).Get()); + }; - if (failedAndLog(hr) && completionHandler) { - completionHandler(make_fl_value()); + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); } } - void CookieManager::getCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, std::function completionHandler) const + void CookieManager::getCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, std::function completionHandler) const { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -221,40 +318,80 @@ namespace flutter_inappwebview_plugin {"urls", std::vector{url}} }; - auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( - [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) { - std::vector cookies = {}; - if (succeededOrLog(errorCode)) { - nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); - auto jsonCookies = json["cookies"].get>(); - for (auto& jsonCookie : jsonCookies) { - cookies.push_back(flutter::EncodableMap{ - {"name", jsonCookie["name"].get()}, - {"value", jsonCookie["value"].get()}, - {"domain", jsonCookie["domain"].get()}, - {"path", jsonCookie["path"].get()}, - {"expiresDate", jsonCookie["expires"].get()}, - {"isHttpOnly", jsonCookie["httpOnly"].get()}, - {"isSecure", jsonCookie["secure"].get()}, - {"isSessionOnly", jsonCookie["session"].get()}, - {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} - }); + if (!webView) { + completionHandler({}); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); } + return; } - if (completionHandler) { - completionHandler(cookies); + + auto hr = webView->CallDevToolsProtocolMethod(L"Network.getCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + std::vector cookies = {}; + if (succeededOrLog(errorCode)) { + nlohmann::json json = nlohmann::json::parse(wide_to_utf8(returnObjectAsJson)); + auto jsonCookies = json["cookies"].get>(); + for (auto& jsonCookie : jsonCookies) { + cookies.push_back(flutter::EncodableMap{ + {"name", jsonCookie["name"].get()}, + {"value", jsonCookie["value"].get()}, + {"domain", jsonCookie["domain"].get()}, + {"path", jsonCookie["path"].get()}, + {"expiresDate", jsonCookie["expires"].get()}, + {"isHttpOnly", jsonCookie["httpOnly"].get()}, + {"isSecure", jsonCookie["secure"].get()}, + {"isSessionOnly", jsonCookie["session"].get()}, + {"sameSite", jsonCookie.contains("sameSite") ? jsonCookie["sameSite"].get() : make_fl_value()} + }); + } + } + + if (completionHandler) { + completionHandler(cookies); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler({}); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } } - return S_OK; - } - ).Get()); + }; - if (failedAndLog(hr) && completionHandler) { - completionHandler({}); + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); } } - void CookieManager::deleteCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const + void CookieManager::deleteCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -272,22 +409,61 @@ namespace flutter_inappwebview_plugin parameters["domain"] = domain.value(); } - auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.deleteCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( - [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) { - if (completionHandler) { - completionHandler(succeededOrLog(errorCode)); + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; } - return S_OK; - } - ).Get()); - if (failedAndLog(hr) && completionHandler) { - completionHandler(false); + auto hr = webView->CallDevToolsProtocolMethod(L"Network.deleteCookies", utf8_to_wide(parameters.dump()).c_str(), Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + if (inAppWebView && inAppWebView->webView) { + callback(nullptr, inAppWebView->webView, false); + } + else { + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); } } - void CookieManager::deleteCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const + void CookieManager::deleteCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -296,7 +472,7 @@ namespace flutter_inappwebview_plugin return; } - getCookies(webViewEnvironment, url, [this, webViewEnvironment, url, path, domain, completionHandler](const flutter::EncodableList& cookies) + getCookies(webViewEnvironment, inAppWebView, url, [this, webViewEnvironment, inAppWebView, url, path, domain, completionHandler](const flutter::EncodableList& cookies) { auto callbacksComplete = std::make_shared>( [completionHandler](const std::vector& values) @@ -309,7 +485,7 @@ namespace flutter_inappwebview_plugin for (auto& cookie : cookies) { auto cookieMap = std::get(cookie); auto name = get_fl_map_value(cookieMap, "name"); - deleteCookie(webViewEnvironment, url, name, path, domain, [callbacksComplete](const bool& deleted) + deleteCookie(webViewEnvironment, inAppWebView, url, name, path, domain, [callbacksComplete](const bool& deleted) { callbacksComplete->addValue(deleted); }); @@ -317,7 +493,7 @@ namespace flutter_inappwebview_plugin }); } - void CookieManager::deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) const + void CookieManager::deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) { if (!plugin || !plugin->webViewEnvironmentManager) { if (completionHandler) { @@ -326,19 +502,53 @@ namespace flutter_inappwebview_plugin return; } - auto hr = webViewEnvironment->getWebView()->CallDevToolsProtocolMethod(L"Network.clearBrowserCookies", L"{}", Callback( - [completionHandler](HRESULT errorCode, LPCWSTR returnObjectAsJson) + auto callback = [=](wil::com_ptr webViewController, wil::com_ptr webView, bool shouldDestroyWebView) { - if (completionHandler) { - completionHandler(succeededOrLog(errorCode)); + if (!webView) { + completionHandler(false); + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + return; } - return S_OK; - } - ).Get()); - if (failedAndLog(hr) && completionHandler) { - completionHandler(false); - } + auto hr = webView->CallDevToolsProtocolMethod(L"Network.clearBrowserCookies", L"{}", Callback( + [=](HRESULT errorCode, LPCWSTR returnObjectAsJson) + { + if (completionHandler) { + completionHandler(succeededOrLog(errorCode)); + } + + if (shouldDestroyWebView && webViewController) { + // for an unknown reason, destroying the temp webview just here, after + // the execution of this method, causes a crash inside ICoreWebView2, + // so we destroy it just after 1 second. + Timer::setTimeout([webViewController]() + { + if (webViewController) { + webViewController->Close(); + } + }, 1000); + } + + return S_OK; + } + ).Get()); + + if (failedAndLog(hr)) { + if (completionHandler) { + completionHandler(false); + } + if (shouldDestroyWebView && webViewController) { + webViewController->Close(); + } + } + }; + + webViewEnvironment->useTempWebView([callback](wil::com_ptr webViewController, wil::com_ptr webView) + { + callback(webViewController, webView, true); + }); } CookieManager::~CookieManager() diff --git a/flutter_inappwebview_windows/windows/cookie_manager.h b/flutter_inappwebview_windows/windows/cookie_manager.h index 313fd8a2c..db0561247 100644 --- a/flutter_inappwebview_windows/windows/cookie_manager.h +++ b/flutter_inappwebview_windows/windows/cookie_manager.h @@ -7,6 +7,7 @@ #include #include "flutter_inappwebview_windows_plugin.h" +#include "in_app_webview/in_app_webview.h" #include "types/channel_delegate.h" #include "webview_environment/webview_environment_manager.h" @@ -26,12 +27,12 @@ namespace flutter_inappwebview_plugin const flutter::MethodCall& method_call, std::unique_ptr> result); - void setCookie(WebViewEnvironment* webViewEnvironment, const flutter::EncodableMap& map, std::function completionHandler) const; - void getCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, std::function completionHandler) const; - void getCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, std::function completionHandler) const; - void deleteCookie(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const; - void deleteCookies(WebViewEnvironment* webViewEnvironment, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const; - void deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler) const; + void setCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const flutter::EncodableMap& map, std::function completionHandler) const; + void getCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, std::function completionHandler) const; + void getCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, std::function completionHandler) const; + void deleteCookie(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& name, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteCookies(WebViewEnvironment* webViewEnvironment, InAppWebView* inAppWebView, const std::string& url, const std::string& path, const std::optional& domain, std::function completionHandler) const; + void deleteAllCookies(WebViewEnvironment* webViewEnvironment, std::function completionHandler); }; } diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc index 1b6a3ff88..f990fcc44 100644 --- a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc @@ -186,12 +186,21 @@ namespace flutter_inappwebview_plugin event_channel_->SetStreamHandler(std::move(handler)); } + void CustomPlatformView::UnregisterMethodCallHandler() const + { + if (method_channel_) { + method_channel_->SetMethodCallHandler(nullptr); + if (view && view->channelDelegate) { + view->channelDelegate->UnregisterMethodCallHandler(); + } + } + } + CustomPlatformView::~CustomPlatformView() { debugLog("dealloc CustomPlatformView"); - method_channel_->SetMethodCallHandler(nullptr); event_sink_ = nullptr; - texture_registrar_->UnregisterTexture(texture_id_); + texture_registrar_->UnregisterTexture(texture_id_, nullptr); } void CustomPlatformView::RegisterEventHandlers() @@ -273,14 +282,15 @@ namespace flutter_inappwebview_plugin if (method_name.compare(kMethodSetPointerButton) == 0) { const auto& map = std::get(*method_call.arguments()); + const auto kind = map.find(flutter::EncodableValue("kind")); const auto button = map.find(flutter::EncodableValue("button")); - const auto isDown = map.find(flutter::EncodableValue("isDown")); - if (button != map.end() && isDown != map.end()) { + if (kind != map.end() && button != map.end()) { + const auto kindValue = std::get_if(&kind->second); const auto buttonValue = std::get_if(&button->second); - const auto isDownValue = std::get_if(&isDown->second); - if (buttonValue && isDownValue && view) { + if (kindValue && buttonValue && view) { view->setPointerButtonState( - static_cast(*buttonValue), *isDownValue); + static_cast(*kindValue), + static_cast(*buttonValue)); return result->Success(); } } diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h index 35946f340..65db5476a 100644 --- a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h @@ -29,6 +29,8 @@ namespace flutter_inappwebview_plugin TextureBridge* texture_bridge() const { return texture_bridge_.get(); } int64_t texture_id() const { return texture_id_; } + + void UnregisterMethodCallHandler() const; private: HWND hwnd_; std::unique_ptr flutter_texture_; diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp index a616a89bd..ac829ae9f 100644 --- a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp @@ -6,11 +6,15 @@ #include "headless_in_app_webview/headless_in_app_webview_manager.h" #include "in_app_browser/in_app_browser_manager.h" #include "in_app_webview/in_app_webview_manager.h" +#include "platform_util.h" #include "webview_environment/webview_environment_manager.h" + #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "rpcrt4.lib") // UuidCreate - Minimum supported OS Win 2000 +#pragma comment(lib, "WindowsApp.lib") namespace flutter_inappwebview_plugin { @@ -30,8 +34,41 @@ namespace flutter_inappwebview_plugin inAppBrowserManager = std::make_unique(this); headlessInAppWebViewManager = std::make_unique(this); cookieManager = std::make_unique(this); + platformUtil = std::make_unique(this); + + window_proc_id = registrar->RegisterTopLevelWindowProcDelegate( + [this](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + return HandleWindowProc(hWnd, message, wParam, lParam); + }); } FlutterInappwebviewWindowsPlugin::~FlutterInappwebviewWindowsPlugin() - {} + { + if (registrar) { + registrar->UnregisterTopLevelWindowProcDelegate(window_proc_id); + } + webViewEnvironmentManager = nullptr; + inAppWebViewManager = nullptr; + inAppBrowserManager = nullptr; + headlessInAppWebViewManager = nullptr; + cookieManager = nullptr; + platformUtil = nullptr; + } + + + std::optional FlutterInappwebviewWindowsPlugin::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) + { + std::optional result = std::nullopt; + + if (platformUtil) { + result = platformUtil->HandleWindowProc(hWnd, message, wParam, lParam); + } + + return result; + } } \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h index 9d0bd4032..b195a5b34 100644 --- a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h @@ -1,7 +1,6 @@ #ifndef FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ #define FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ -#include #include namespace flutter_inappwebview_plugin @@ -11,6 +10,7 @@ namespace flutter_inappwebview_plugin class InAppBrowserManager; class HeadlessInAppWebViewManager; class CookieManager; + class PlatformUtil; class FlutterInappwebviewWindowsPlugin : public flutter::Plugin { public: @@ -20,6 +20,7 @@ namespace flutter_inappwebview_plugin std::unique_ptr inAppBrowserManager; std::unique_ptr headlessInAppWebViewManager; std::unique_ptr cookieManager; + std::unique_ptr platformUtil; static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); @@ -30,6 +31,14 @@ namespace flutter_inappwebview_plugin // Disallow copy and assign. FlutterInappwebviewWindowsPlugin(const FlutterInappwebviewWindowsPlugin&) = delete; FlutterInappwebviewWindowsPlugin& operator=(const FlutterInappwebviewWindowsPlugin&) = delete; + private: + // The ID of the WindowProc delegate registration. + int window_proc_id = -1; + std::optional FlutterInappwebviewWindowsPlugin::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); }; } #endif // FLUTTER_PLUGIN_FLUTTER_INAPPWEBVIEW_PLUGIN_PLUGIN_H_ diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp index e5ff4c447..a74587d59 100644 --- a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_in_app_webview_manager.cpp @@ -46,6 +46,11 @@ namespace flutter_inappwebview_plugin { auto result_ = std::shared_ptr>(std::move(result)); + if (!plugin) { + result_->Error("0", "Cannot create the HeadlessInAppWebView instance!"); + return; + } + auto id = get_fl_map_value(*arguments, "id"); auto params = get_fl_map_value(*arguments, "params"); @@ -80,7 +85,7 @@ namespace flutter_inappwebview_plugin wil::com_ptr webViewController, wil::com_ptr webViewCompositionController) { - if (webViewEnv && webViewController) { + if (plugin && webViewEnv && webViewController) { std::optional>> initialUserScripts = initialUserScriptList.has_value() ? functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : std::optional>>{}; diff --git a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp index 5aadab56c..95099be68 100644 --- a/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/headless_in_app_webview/headless_webview_channel_delegate.cpp @@ -29,6 +29,12 @@ namespace flutter_inappwebview_plugin std::map>& webViews = webView->plugin->headlessInAppWebViewManager->webViews; auto& id = webView->id; if (map_contains(webViews, id)) { + if (webView->channelDelegate) { + webView->channelDelegate->UnregisterMethodCallHandler(); + if (webView->webView && webView->webView->channelDelegate) { + webView->webView->channelDelegate->UnregisterMethodCallHandler(); + } + } webViews.erase(id); } } diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp index ec9ad3900..d85f11102 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp @@ -200,12 +200,18 @@ namespace flutter_inappwebview_plugin if (!destroyed_) { destroyed_ = true; - webView.reset(); - if (channelDelegate) { channelDelegate->onExit(); } + if (channelDelegate) { + channelDelegate->UnregisterMethodCallHandler(); + if (webView && webView->channelDelegate) { + webView->channelDelegate->UnregisterMethodCallHandler(); + } + } + webView.reset(); + if (plugin && plugin->inAppBrowserManager) { plugin->inAppBrowserManager->browsers.erase(id); } diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp index 461300679..7f210f7bb 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp @@ -25,8 +25,14 @@ namespace flutter_inappwebview_plugin auto& methodName = method_call.method_name(); if (string_equals(methodName, "open")) { - createInAppBrowser(arguments); - result->Success(true); + if (plugin) { + createInAppBrowser(arguments); + result->Success(true); + } + else { + result->Error("0", "Cannot create the InAppBrowser instance!"); + + } } else if (string_equals(methodName, "openWithSystemBrowser")) { auto url = get_fl_map_value(*arguments, "url"); diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h index 1243ca2b9..d5be8ee56 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_settings.h @@ -11,7 +11,7 @@ namespace flutter_inappwebview_plugin { class InAppBrowser; - enum InAppBrowserWindowType { + enum class InAppBrowserWindowType { window, child }; @@ -20,7 +20,7 @@ namespace flutter_inappwebview_plugin { public: bool hidden = false; - InAppBrowserWindowType windowType = window; + InAppBrowserWindowType windowType = InAppBrowserWindowType::window; std::string toolbarTopFixedTitle; double windowAlphaValue = 1.0; std::shared_ptr windowFrame; diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp index f222a7ac9..950d245c9 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp @@ -1,12 +1,20 @@ #include #include #include +#include #include #include +#include #include "../custom_platform_view/util/composition.desktop.interop.h" #include "../plugin_scripts_js/javascript_bridge_js.h" +#include "../types/client_cert_response.h" #include "../types/create_window_action.h" +#include "../types/http_auth_response.h" +#include "../types/javascript_handler_function_data.h" +#include "../types/server_trust_auth_response.h" +#include "../types/ssl_error.h" +#include "../types/url_credential.h" #include "../types/web_resource_error.h" #include "../types/web_resource_request.h" #include "../utils/base64.h" @@ -14,7 +22,7 @@ #include "../utils/map.h" #include "../utils/strconv.h" #include "../utils/string.h" -#include "../webview_environment/webview_environment_manager.h" +#include "../utils/uri.h" #include "in_app_webview.h" #include "in_app_webview_manager.h" @@ -189,6 +197,8 @@ namespace flutter_inappwebview_plugin return; } + javaScriptBridgeEnabled = settings->javaScriptBridgeEnabled; + wil::com_ptr webView2Settings; auto hrWebView2Settings = webView->get_Settings(&webView2Settings); if (succeededOrLog(hrWebView2Settings)) { @@ -196,18 +206,47 @@ namespace flutter_inappwebview_plugin webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); webView2Settings->put_AreDevToolsEnabled(settings->isInspectable); webView2Settings->put_AreDefaultContextMenusEnabled(!settings->disableContextMenu); + webView2Settings->put_IsBuiltInErrorPageEnabled(!settings->disableDefaultErrorPage); + webView2Settings->put_IsStatusBarEnabled(settings->statusBarEnabled); - wil::com_ptr webView2Settings2; - if (succeededOrLog(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { + if (auto webView2Settings2 = webView2Settings.try_query()) { if (!settings->userAgent.empty()) { webView2Settings2->put_UserAgent(utf8_to_wide(settings->userAgent).c_str()); } } + + if (auto webView2Settings3 = webView2Settings.try_query()) { + webView2Settings3->put_AreBrowserAcceleratorKeysEnabled(settings->browserAcceleratorKeysEnabled); + } + + if (auto webView2Settings4 = webView2Settings.try_query()) { + webView2Settings4->put_IsGeneralAutofillEnabled(settings->generalAutofillEnabled); + webView2Settings4->put_IsPasswordAutosaveEnabled(settings->passwordAutosaveEnabled); + } + + if (auto webView2Settings5 = webView2Settings.try_query()) { + webView2Settings5->put_IsPinchZoomEnabled(settings->pinchZoomEnabled); + } + + if (auto webView2Settings6 = webView2Settings.try_query()) { + webView2Settings6->put_IsSwipeNavigationEnabled(settings->allowsBackForwardNavigationGestures); + } + + if (auto webView2Settings7 = webView2Settings.try_query()) { + webView2Settings7->put_HiddenPdfToolbarItems((COREWEBVIEW2_PDF_TOOLBAR_ITEMS)settings->hiddenPdfToolbarItems); + } + + if (auto webView2Settings8 = webView2Settings.try_query()) { + webView2Settings8->put_IsReputationCheckingRequired(settings->reputationCheckingRequired); + } + + if (auto webView2Settings9 = webView2Settings.try_query()) { + webView2Settings9->put_IsNonClientRegionSupportEnabled(settings->nonClientRegionSupportEnabled); + } } - wil::com_ptr webViewController2; - if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController2)))) { - if (!settings->transparentBackground) { + if (auto webViewController2 = webViewController.try_query()) { + if (settings->transparentBackground) { webViewController2->put_DefaultBackgroundColor({ 0, 255, 255, 255 }); } } @@ -260,7 +299,15 @@ namespace flutter_inappwebview_plugin ).Get())); if (userContentController) { - userContentController->addPluginScript(std::move(createJavaScriptBridgePluginScript())); + if (javaScriptBridgeEnabled) { + auto pluginScriptsOriginAllowList = settings->pluginScriptsOriginAllowList; + auto pluginScriptsForMainFrameOnly = settings->pluginScriptsForMainFrameOnly; + + auto javaScriptBridgeOriginAllowList = settings->javaScriptBridgeOriginAllowList.has_value() ? settings->javaScriptBridgeOriginAllowList : pluginScriptsOriginAllowList; + auto javaScriptBridgeForMainFrameOnly = settings->javaScriptBridgeForMainFrameOnly.has_value() ? settings->javaScriptBridgeForMainFrameOnly.value() : pluginScriptsForMainFrameOnly; + userContentController->addPluginScript(std::move(JavaScriptBridgeJS::JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(expectedBridgeSecret, javaScriptBridgeOriginAllowList, javaScriptBridgeForMainFrameOnly))); + } + if (params.initialUserScripts.has_value()) { userContentController->addUserOnlyScripts(params.initialUserScripts.value()); } @@ -271,14 +318,46 @@ namespace flutter_inappwebview_plugin void InAppWebView::registerEventHandlers() { - if (!webView) { + if (!webView || !webViewController) { return; } - wil::com_ptr fetchRequestPausedEventReceiver; + auto add_AcceleratorKeyPressed_HResult = webViewController->add_AcceleratorKeyPressed( + Callback( + [this](ICoreWebView2Controller* sender, ICoreWebView2AcceleratorKeyPressedEventArgs* args) + { + if (channelDelegate) { + auto handled = settings->handleAcceleratorKeyPressed; + args->put_Handled(handled); + if (handled) { + auto detail = AcceleratorKeyPressedDetail::fromICoreWebView2AcceleratorKeyPressedEventArgs(args); + channelDelegate->onAcceleratorKeyPressed(std::move(detail)); + } + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_AcceleratorKeyPressed_HResult); + + auto add_ZoomFactorChanged_HResult = webViewController->add_ZoomFactorChanged( + Callback( + [this](ICoreWebView2Controller* sender, IUnknown* args) + { + double newScale; + if (succeededOrLog(sender->get_ZoomFactor(&newScale))) { + if (channelDelegate) { + channelDelegate->onZoomScaleChanged(zoomScaleFactor_, newScale); + } + zoomScaleFactor_ = newScale; + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ZoomFactorChanged_HResult); + wil::com_ptr fetchRequestPausedEventReceiver; if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(L"Fetch.requestPaused", &fetchRequestPausedEventReceiver))) { - failedAndLog(fetchRequestPausedEventReceiver->add_DevToolsProtocolEventReceived( + auto add_DevToolsProtocolEventReceived_HResult = fetchRequestPausedEventReceiver->add_DevToolsProtocolEventReceived( Callback( [this]( ICoreWebView2* sender, @@ -412,10 +491,11 @@ namespace flutter_inappwebview_plugin return S_OK; }) - .Get(), nullptr)); + .Get(), nullptr); + failedAndLog(add_DevToolsProtocolEventReceived_HResult); } - failedLog(webView->add_NavigationStarting( + auto add_NavigationStarting_HResult = webView->add_NavigationStarting( Callback( [this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) { @@ -515,9 +595,10 @@ namespace flutter_inappwebview_plugin return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_NavigationStarting_HResult); - failedLog(webView->add_ContentLoading( + auto add_ContentLoading_HResult = webView->add_ContentLoading( Callback( [this](ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) { @@ -526,15 +607,17 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_ContentLoading_HResult); - failedLog(webView->add_NavigationCompleted( + auto add_NavigationCompleted_HResult = webView->add_NavigationCompleted( Callback( [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) { isLoading_ = false; + previousAuthRequestFailureCount = 0; - evaluateJavascript(PLATFORM_READY_JS_SOURCE, ContentWorld::page(), nullptr); + evaluateJavascript(JavaScriptBridgeJS::PLATFORM_READY_JS_SOURCE(), ContentWorld::page(), nullptr); std::shared_ptr navigationAction; UINT64 navigationId; @@ -581,9 +664,10 @@ namespace flutter_inappwebview_plugin return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_NavigationCompleted_HResult); - failedLog(webView->add_DocumentTitleChanged(Callback( + auto add_DocumentTitleChanged_HResult = webView->add_DocumentTitleChanged(Callback( [this](ICoreWebView2* sender, IUnknown* args) { if (channelDelegate) { @@ -593,9 +677,10 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_DocumentTitleChanged_HResult); - failedLog(webView->add_HistoryChanged(Callback( + auto add_HistoryChanged_HResult = webView->add_HistoryChanged(Callback( [this](ICoreWebView2* sender, IUnknown* args) { if (channelDelegate) { @@ -607,63 +692,20 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_HistoryChanged_HResult); - failedLog(webView->add_WebMessageReceived(Callback( + auto add_WebMessageReceived_HResult = webView->add_WebMessageReceived(Callback( [this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) { - if (!channelDelegate) { - return S_OK; - } - - wil::unique_cotaskmem_string json; - if (succeededOrLog(args->get_WebMessageAsJson(&json))) { - auto message = nlohmann::json::parse(wide_to_utf8(json.get())); - - if (message.is_object() && message.contains("name") && message.at("name").is_string() && message.contains("body") && message.at("body").is_object()) { - auto name = message.at("name").get(); - auto body = message.at("body").get(); - - if (name.compare("callHandler") == 0 && body.contains("handlerName") && body.at("handlerName").is_string()) { - auto handlerName = body.at("handlerName").get(); - auto callHandlerID = body.at("_callHandlerID").is_number_integer() ? body.at("_callHandlerID").get() : 0; - std::string handlerArgs = body.at("args").is_string() ? body.at("args").get() : ""; - - auto callback = std::make_unique(); - callback->defaultBehaviour = [this, callHandlerID](const std::optional response) - { - std::string json = "null"; - if (response.has_value() && !response.value()->IsNull()) { - json = std::get(*(response.value())); - } - - evaluateJavascript("if (window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "] != null) { \ - window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "].resolve(" + json + "); \ - delete window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "]; \ - }", ContentWorld::page(), nullptr); - }; - callback->error = [this, callHandlerID](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) - { - auto errorMessage = error_code + ", " + error_message; - debugLog(errorMessage); - - evaluateJavascript("if (window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "] != null) { \ - window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "].reject(new Error('" + replace_all_copy(errorMessage, "\'", "\\'") + "')); \ - delete window." + JAVASCRIPT_BRIDGE_NAME + "[" + std::to_string(callHandlerID) + "]; \ - }", ContentWorld::page(), nullptr); - }; - channelDelegate->onCallJsHandler(handlerName, handlerArgs, std::move(callback)); - } - } - } - - return S_OK; + return this->onCallJsHandler(true, args); } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_WebMessageReceived_HResult); wil::com_ptr consoleMessageReceiver; if (succeededOrLog(webView->GetDevToolsProtocolEventReceiver(L"Runtime.consoleAPICalled", &consoleMessageReceiver))) { - failedLog(consoleMessageReceiver->add_DevToolsProtocolEventReceived( + auto consoleMessageReceiver_add_DevToolsProtocolEventReceived_HResult = consoleMessageReceiver->add_DevToolsProtocolEventReceived( Callback( [this]( ICoreWebView2* sender, @@ -703,10 +745,11 @@ namespace flutter_inappwebview_plugin return S_OK; }) - .Get(), nullptr)); + .Get(), nullptr); + failedLog(consoleMessageReceiver_add_DevToolsProtocolEventReceived_HResult); } - failedLog(webView->add_NewWindowRequested( + auto add_NewWindowRequested_HResult = webView->add_NewWindowRequested( Callback( [this](ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args) { @@ -763,9 +806,10 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_NewWindowRequested_HResult); - failedLog(webView->add_WindowCloseRequested(Callback( + auto add_WindowCloseRequested_HResult = webView->add_WindowCloseRequested(Callback( [this](ICoreWebView2* sender, IUnknown* args) { if (channelDelegate) { @@ -773,9 +817,10 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_WindowCloseRequested_HResult); - failedLog(webView->add_PermissionRequested(Callback( + auto add_PermissionRequested_HResult = webView->add_PermissionRequested(Callback( [this](ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args) { wil::com_ptr deferral; @@ -784,7 +829,7 @@ namespace flutter_inappwebview_plugin std::string url = SUCCEEDED(args->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : ""; COREWEBVIEW2_PERMISSION_KIND resource = COREWEBVIEW2_PERMISSION_KIND_UNKNOWN_PERMISSION; - failedAndLog(args->get_PermissionKind(&resource)); + failedLog(args->get_PermissionKind(&resource)); auto callback = std::make_unique(); auto defaultBehaviour = [this, deferral, args](const std::optional> permissionResponse) @@ -822,10 +867,11 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_PermissionRequested_HResult); failedLog(webView->AddWebResourceRequestedFilter(L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)); - failedLog(webView->add_WebResourceRequested( + auto add_WebResourceRequested_HResult = webView->add_WebResourceRequested( Callback( [this]( ICoreWebView2* sender, ICoreWebView2WebResourceRequestedEventArgs* args) @@ -907,11 +953,80 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_WebResourceRequested_HResult); + + auto add_ProcessFailed_HResult = webView->add_ProcessFailed( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* argsRaw) + { + if (!channelDelegate) { + return S_OK; + } + + wil::com_ptr args = argsRaw; + auto args2 = args.try_query(); + auto args3 = args.try_query(); + + COREWEBVIEW2_PROCESS_FAILED_REASON reason = COREWEBVIEW2_PROCESS_FAILED_REASON_UNEXPECTED; + if (args2) { + args2->get_Reason(&reason); + } + + COREWEBVIEW2_PROCESS_FAILED_KIND kind; + if (succeededOrLog(args->get_ProcessFailedKind(&kind))) { + if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED) { + auto didCrash = reason == COREWEBVIEW2_PROCESS_FAILED_REASON_CRASHED; + auto detail = std::make_unique( + didCrash + ); + channelDelegate->onRenderProcessGone(std::move(detail)); + } + else if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE) { + channelDelegate->onRenderProcessUnresponsive(getUrl()); + } + else if (kind == COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED) { + channelDelegate->onWebContentProcessDidTerminate(); + } + + auto frameInfos = std::optional>>{}; + wil::com_ptr frameInfoCollection; + wil::com_ptr frameIterator; + if (args2 && succeededOrLog(args2->get_FrameInfosForFailedProcess(&frameInfoCollection)) && frameInfoCollection && succeededOrLog(frameInfoCollection->GetIterator(&frameIterator))) { + frameInfos = std::vector>{}; + BOOL hasCurrent = FALSE; + while (SUCCEEDED(frameIterator->MoveNext(&hasCurrent)) && hasCurrent) { + wil::com_ptr frameInfo; + if (SUCCEEDED(frameIterator->GetCurrent(&frameInfo))) { + frameInfos.value().push_back(std::move(FrameInfo::fromICoreWebView2FrameInfo(frameInfo))); + } + BOOL hasNext = FALSE; + failedLog(frameIterator->MoveNext(&hasNext)); + } + } + + wil::unique_cotaskmem_string processDescription; + int exitCode; + wil::unique_cotaskmem_string failedModule; + + auto detail = std::make_unique( + (int64_t)kind, + args2 && succeededOrLog(args2->get_ExitCode(&exitCode)) ? exitCode : std::optional{}, + args2 && succeededOrLog(args2->get_ProcessDescription(&processDescription)) ? wide_to_utf8(processDescription.get()) : std::optional{}, + args2 ? (int64_t)reason : std::optional{}, + args3 && succeededOrLog(args3->get_FailureSourceModulePath(&failedModule)) ? wide_to_utf8(failedModule.get()) : std::optional{}, + frameInfos + ); + channelDelegate->onProcessFailed(std::move(detail)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_ProcessFailed_HResult); wil::com_ptr webView2; if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView2)))) { - failedLog(webView2->add_DOMContentLoaded( + auto add_DOMContentLoaded_HResult = webView2->add_DOMContentLoaded( Callback( [this](ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args) { @@ -920,27 +1035,389 @@ namespace flutter_inappwebview_plugin } return S_OK; } - ).Get(), nullptr)); + ).Get(), nullptr); + failedLog(add_DOMContentLoaded_HResult); + } + + wil::com_ptr webView4; + if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView4)))) { + auto add_FrameCreated_HResult = webView4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) + { + wil::com_ptr frame; + wil::com_ptr frame2; + if (succeededOrLog(args->get_Frame(&frame)) && SUCCEEDED(frame->QueryInterface(IID_PPV_ARGS(&frame2)))) { + auto frame_add_WebMessageReceived_HResult = frame2->add_WebMessageReceived(Callback( + [this](ICoreWebView2Frame* sender, ICoreWebView2WebMessageReceivedEventArgs* args) + { + return this->onCallJsHandler(false, args); + }).Get(), nullptr); + failedLog(frame_add_WebMessageReceived_HResult); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_FrameCreated_HResult); + + auto add_DownloadStarting_HResult = webView4->add_DownloadStarting( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2DownloadStartingEventArgs* args) + { + wil::com_ptr deferral; + wil::com_ptr download; + if (channelDelegate && settings->useOnDownloadStart && succeededOrLog(args->get_DownloadOperation(&download)) && succeededOrLog(args->GetDeferral(&deferral))) { + + wil::unique_cotaskmem_string uri; + std::string url = SUCCEEDED(download->get_Uri(&uri)) ? wide_to_utf8(uri.get()) : ""; + + INT64 contentLength = 0; + failedLog(download->get_TotalBytesToReceive(&contentLength)); + + wil::unique_cotaskmem_string downloadMimeType; + std::optional mimeType = SUCCEEDED(download->get_MimeType(&downloadMimeType)) ? wide_to_utf8(downloadMimeType.get()) : std::optional{}; + + wil::unique_cotaskmem_string downloadContentDisposition; + std::optional contentDisposition = SUCCEEDED(download->get_ContentDisposition(&downloadContentDisposition)) ? wide_to_utf8(downloadContentDisposition.get()) : std::optional{}; + + wil::unique_cotaskmem_string resultFilePath; + std::optional suggestedFilename = SUCCEEDED(download->get_ContentDisposition(&resultFilePath)) ? wide_to_utf8(resultFilePath.get()) : std::optional{}; + + auto request = std::make_shared( + contentDisposition, + contentLength, + mimeType, + suggestedFilename, + url + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + failedLog(args->put_Handled(response->handled)); + auto resultFilePath = response->resultFilePath; + if (resultFilePath.has_value()) { + failedLog(args->put_ResultFilePath(utf8_to_wide(resultFilePath.value()).c_str())); + } + auto action = response->action; + if (action.has_value()) { + switch (action.value()) { + case DownloadStartResponseAction::cancel: + failedLog(args->put_Cancel(true)); + break; + } + } + failedLog(deferral->Complete()); + return false; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onDownloadStarting(std::move(request), std::move(callback)); + } + return S_OK; + } + ).Get(), nullptr); + failedLog(add_DownloadStarting_HResult); + } + + if (auto webView5 = webView.try_query()) { + auto add_ClientCertificateRequested_HResult = webView5->add_ClientCertificateRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2ClientCertificateRequestedEventArgs* args) + { + wil::com_ptr deferral; + wil::unique_cotaskmem_string host; + + if (channelDelegate && plugin && plugin->inAppWebViewManager && + succeededOrLog(args->get_Host(&host)) && succeededOrLog(args->GetDeferral(&deferral))) { + + std::vector> mutuallyTrustedCertificates = {}; + wil::com_ptr certificateCollection; + uint32_t certCount = 0; + if (succeededOrLog(args->get_MutuallyTrustedCertificates(&certificateCollection)) && succeededOrLog(certificateCollection->get_Count(&certCount))) { + + for (uint32_t i = 0; i < certCount; i++) { + wil::com_ptr clientCert; + if (succeededOrLog(certificateCollection->GetValueAtIndex(i, &clientCert))) { + wil::unique_cotaskmem_string certPemEncoded; + if (succeededOrLog(clientCert->ToPemEncoding(&certPemEncoded))) { + mutuallyTrustedCertificates.push_back(std::make_shared(wide_to_utf8(certPemEncoded.get()))); + } + } + } + } + + std::vector allowedCertificateAuthorities = {}; + wil::com_ptr authoritiesCollection; + uint32_t authoritiesCount = 0; + if (succeededOrLog(args->get_AllowedCertificateAuthorities(&authoritiesCollection)) && succeededOrLog(authoritiesCollection->get_Count(&authoritiesCount))) { + for (uint32_t i = 0; i < authoritiesCount; i++) { + wil::unique_cotaskmem_string authority; + if (succeededOrLog(authoritiesCollection->GetValueAtIndex(i, &authority))) { + allowedCertificateAuthorities.push_back(base64_decode(wide_to_utf8(authority.get()))); + } + } + } + + args->get_AllowedCertificateAuthorities(&authoritiesCollection); + + int port = 0; + args->get_Port(&port); + + BOOL isProxy = false; + args->get_IsProxy(&isProxy); + + std::string scheme = ""; + auto currentUrl = getUrl(); + if (currentUrl.has_value()) { + scheme = currentUrl.value().substr(0, currentUrl.value().find(':')); + } + + auto protectionSpace = std::make_unique( + wide_to_utf8(host.get()), + scheme, + std::optional{}, + port, + std::optional>{}, + std::optional>{} + ); + auto challenge = std::make_unique( + std::move(protectionSpace), + allowedCertificateAuthorities, + isProxy, + mutuallyTrustedCertificates + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, certCount, certificateCollection, args](const std::shared_ptr response) + { + auto action = response->action; + + if (action.has_value()) { + switch (action.value()) { + case ClientCertResponseAction::proceed: + if (certCount > 0 && response->selectedCertificate >= 0) { + wil::com_ptr selectedClientCert; + if (succeededOrLog(certificateCollection->GetValueAtIndex((uint32_t)response->selectedCertificate, &selectedClientCert))) { + args->put_SelectedCertificate(selectedClientCert.get()); + } + } + args->put_Handled(true); + args->put_Cancel(false); + break; + case ClientCertResponseAction::ignore: + args->put_Handled(true); + args->put_Cancel(false); + break; + case ClientCertResponseAction::cancel: + default: + args->put_Cancel(true); + break; + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedClientCertRequest(std::move(challenge), std::move(callback)); + } + return S_OK; + }) + .Get(), nullptr); + failedLog(add_ClientCertificateRequested_HResult); } - /* - wil::com_ptr webView14; - if (SUCCEEDED(webView->QueryInterface(IID_PPV_ARGS(&webView14)))) { - failedLog(webView14->add_ServerCertificateErrorDetected( + if (auto webView10 = webView.try_query()) { + auto add_BasicAuthenticationRequested_HResult = webView10->add_BasicAuthenticationRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2BasicAuthenticationRequestedEventArgs* args) + { + wil::com_ptr deferral; + wil::com_ptr basicAuthenticationResponse; + wil::unique_cotaskmem_string url; + wil::unique_cotaskmem_string realmChallenge; + + if (channelDelegate && plugin && plugin->inAppWebViewManager && + succeededOrLog(args->get_Uri(&url)) && succeededOrLog(args->get_Challenge(&realmChallenge)) && + succeededOrLog(args->get_Response(&basicAuthenticationResponse)) && succeededOrLog(args->GetDeferral(&deferral))) { + + previousAuthRequestFailureCount++; + + try { + winrt::Windows::Foundation::Uri const uri{ url.get() }; + + auto basicRealm = std::string{ "Basic realm=\"" }; + auto basicRealmLength = basicRealm.length(); + auto realm = wide_to_utf8(realmChallenge.get()); + if (starts_with(realm, basicRealm)) { + realm = realm.substr(basicRealmLength, realm.length() - basicRealmLength - 1); + } + + auto protectionSpace = std::make_unique( + wide_to_utf8(uri.Host().c_str()), + wide_to_utf8(uri.SchemeName().c_str()), + realm, + uri.Port(), + std::optional>{}, + std::optional>{} + ); + auto challenge = std::make_unique( + std::move(protectionSpace), + previousAuthRequestFailureCount, + std::optional>{} + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, basicAuthenticationResponse, args](const std::shared_ptr response) + { + auto action = response->action; + std::wstring username = utf8_to_wide(response->username); + std::wstring password = utf8_to_wide(response->password); + + if (action.has_value()) { + switch (action.value()) { + case HttpAuthResponseAction::proceed: + failedLog(basicAuthenticationResponse->put_UserName(username.c_str())); + failedLog(basicAuthenticationResponse->put_Password(password.c_str())); + break; + case HttpAuthResponseAction::cancel: + default: + args->put_Cancel(true); + break; + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedHttpAuthRequest(std::move(challenge), std::move(callback)); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } + } + return S_OK; + }) + .Get(), nullptr); + failedLog(add_BasicAuthenticationRequested_HResult); + } + + if (auto webView14 = webView.try_query()) { + auto add_ServerCertificateErrorDetected_HResult = webView14->add_ServerCertificateErrorDetected( Callback( [this](ICoreWebView2* sender, ICoreWebView2ServerCertificateErrorDetectedEventArgs* args) { - debugLog("add_ServerCertificateErrorDetected"); - wil::com_ptr certificate = nullptr; - if (SUCCEEDED(args->get_ServerCertificate(&certificate))) { - wil::unique_cotaskmem_string displayName = nullptr; - std::optional url = SUCCEEDED(certificate->get_DisplayName(&displayName)) ? wide_to_utf8(displayName.get()) : std::optional{}; - debugLog(displayName.get()); + wil::com_ptr deferral; + wil::unique_cotaskmem_string requestUrl; + if (succeededOrLog(args->get_RequestUri(&requestUrl)) && succeededOrLog(args->GetDeferral(&deferral))) { + + wil::com_ptr serverCert; + auto sslCert = std::optional>{}; + if (succeededOrLog(args->get_ServerCertificate(&serverCert))) { + wil::unique_cotaskmem_string certPemEncoded; + if (succeededOrLog(serverCert->ToPemEncoding(&certPemEncoded))) { + sslCert = std::make_shared(wide_to_utf8(certPemEncoded.get())); + } + } + + auto sslError = std::optional>{}; + COREWEBVIEW2_WEB_ERROR_STATUS errorStatus; + if (succeededOrLog(args->get_ErrorStatus(&errorStatus))) { + sslError = std::make_shared( + errorStatus, + COREWEBVIEW2_WEB_ERROR_STATUS_ToString(errorStatus) + ); + } + + try { + winrt::Windows::Foundation::Uri const uri{ requestUrl.get() }; + + auto protectionSpace = std::make_unique( + wide_to_utf8(uri.Host().c_str()), + wide_to_utf8(uri.SchemeName().c_str()), + std::optional{}, + uri.Port(), + sslCert, + sslError + ); + auto challenge = std::make_unique( + std::move(protectionSpace) + ); + + auto callback = std::make_unique(); + auto defaultBehaviour = [this, deferral, args](const std::optional> response) + { + failedLog(deferral->Complete()); + }; + callback->nonNullSuccess = [this, deferral, args](const std::shared_ptr response) + { + auto action = response->action; + + if (action.has_value()) { + switch (action.value()) { + case ServerTrustAuthResponseAction::proceed: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW); + break; + case ServerTrustAuthResponseAction::cancel: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_CANCEL); + break; + default: + args->put_Action(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_DEFAULT); + } + failedLog(deferral->Complete()); + return false; + } + return true; + }; + callback->defaultBehaviour = defaultBehaviour; + callback->error = [this, defaultBehaviour](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + debugLog(error_code + ", " + error_message); + defaultBehaviour(std::nullopt); + }; + channelDelegate->onReceivedServerTrustAuthRequest(std::move(challenge), std::move(callback)); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } } return S_OK; } - ).Get(), nullptr)); - }*/ + ).Get(), nullptr); + failedLog(add_ServerCertificateErrorDetected_HResult); + } if (userContentController) { userContentController->registerEventHandlers(); @@ -1430,16 +1907,67 @@ namespace flutter_inappwebview_plugin webView2Settings->put_AreDefaultContextMenusEnabled(!newSettings->disableContextMenu); } - wil::com_ptr webView2Settings2; - if (succeededOrLog(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { + if (fl_map_contains_not_null(newSettingsMap, "disableDefaultErrorPage") && settings->disableDefaultErrorPage != newSettings->disableDefaultErrorPage) { + webView2Settings->put_IsBuiltInErrorPageEnabled(!newSettings->disableDefaultErrorPage); + } + + if (fl_map_contains_not_null(newSettingsMap, "statusBarEnabled") && settings->statusBarEnabled != newSettings->statusBarEnabled) { + webView2Settings->put_IsStatusBarEnabled(newSettings->statusBarEnabled); + } + + if (auto webView2Settings2 = webView2Settings.try_query()) { if (fl_map_contains_not_null(newSettingsMap, "userAgent") && !string_equals(settings->userAgent, newSettings->userAgent)) { webView2Settings2->put_UserAgent(utf8_to_wide(newSettings->userAgent).c_str()); } } + + if (auto webView2Settings3 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "browserAcceleratorKeysEnabled") && settings->browserAcceleratorKeysEnabled != newSettings->browserAcceleratorKeysEnabled) { + webView2Settings3->put_AreBrowserAcceleratorKeysEnabled(newSettings->browserAcceleratorKeysEnabled); + } + } + + if (auto webView2Settings4 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "generalAutofillEnabled") && settings->generalAutofillEnabled != newSettings->generalAutofillEnabled) { + webView2Settings4->put_IsGeneralAutofillEnabled(newSettings->generalAutofillEnabled); + } + if (fl_map_contains_not_null(newSettingsMap, "passwordAutosaveEnabled") && settings->passwordAutosaveEnabled != newSettings->passwordAutosaveEnabled) { + webView2Settings4->put_IsPasswordAutosaveEnabled(newSettings->passwordAutosaveEnabled); + } + } + + if (auto webView2Settings5 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "pinchZoomEnabled") && settings->pinchZoomEnabled != newSettings->pinchZoomEnabled) { + webView2Settings5->put_IsPinchZoomEnabled(newSettings->pinchZoomEnabled); + } + } + + if (auto webView2Settings6 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "allowsBackForwardNavigationGestures") && settings->allowsBackForwardNavigationGestures != newSettings->allowsBackForwardNavigationGestures) { + webView2Settings6->put_IsSwipeNavigationEnabled(newSettings->allowsBackForwardNavigationGestures); + } + } + + if (auto webView2Settings7 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "hiddenPdfToolbarItems") && settings->hiddenPdfToolbarItems != newSettings->hiddenPdfToolbarItems) { + webView2Settings7->put_HiddenPdfToolbarItems((COREWEBVIEW2_PDF_TOOLBAR_ITEMS)newSettings->hiddenPdfToolbarItems); + } + } + + if (auto webView2Settings8 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "reputationCheckingRequired") && settings->reputationCheckingRequired != newSettings->reputationCheckingRequired) { + webView2Settings8->put_IsReputationCheckingRequired(newSettings->reputationCheckingRequired); + } + } + + if (auto webView2Settings9 = webView2Settings.try_query()) { + if (fl_map_contains_not_null(newSettingsMap, "nonClientRegionSupportEnabled") && settings->nonClientRegionSupportEnabled != newSettings->nonClientRegionSupportEnabled) { + webView2Settings9->put_IsNonClientRegionSupportEnabled(newSettings->nonClientRegionSupportEnabled); + } + } } - wil::com_ptr webViewController2; - if (succeededOrLog(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController2)))) { + if (auto webViewController2 = webViewController.try_query()) { if (fl_map_contains_not_null(newSettingsMap, "transparentBackground") && settings->transparentBackground != newSettings->transparentBackground) { BYTE alpha = newSettings->transparentBackground ? 0 : 255; webViewController2->put_DefaultBackgroundColor({ alpha, 255, 255, 255 }); @@ -1599,6 +2127,204 @@ namespace flutter_inappwebview_plugin } } + void InAppWebView::clearSslPreferences(const std::function completionHandler) const + { + if (!webView) { + if (completionHandler) { + completionHandler(); + } + return; + } + + if (auto webView14 = webView.try_query()) { + auto hr = webView14->ClearServerCertificateErrorActions(Callback( + [completionHandler](HRESULT errorCode) + { + failedAndLog(errorCode); + if (completionHandler) { + completionHandler(); + } + return S_OK; + } + ).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(); + } + return; + } + + if (completionHandler) { + completionHandler(); + } + } + + bool InAppWebView::isInterfaceSupported(const std::string& interfaceName) const + { + if (!webView) { + return false; + } + + if (string_equals(interfaceName, "ICoreWebView2") || starts_with(interfaceName, std::string{ "ICoreWebView2_" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_2"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_3"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_4"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_5"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_6"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_7"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_8"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_9"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_10"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_11"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_12"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_13"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_14"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_15"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_16"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_17"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_18"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_19"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_20"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_21"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_22"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_23"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_24"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_25"): + return webView.try_query() != nullptr; + case string_hash("ICoreWebView2_26"): + return webView.try_query() != nullptr; + default: + return false; + } + } + + wil::com_ptr webView2Settings; + if (succeededOrLog(webView->get_Settings(&webView2Settings))) { + if (starts_with(interfaceName, std::string{ "ICoreWebView2Settings" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Settings"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings2"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings3"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings4"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings5"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings6"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings7"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings8"): + return webView2Settings.try_query() != nullptr; + case string_hash("ICoreWebView2Settings9"): + return webView2Settings.try_query() != nullptr; + default: + return false; + } + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Controller" }) && webViewController) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Controller"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller2"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller3"): + return webViewController.try_query() != nullptr; + case string_hash("ICoreWebView2Controller4"): + return webViewController.try_query() != nullptr; + default: + return false; + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2CompositionController" }) && webViewCompositionController) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2CompositionController"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController2"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController3"): + return webViewCompositionController.try_query() != nullptr; + case string_hash("ICoreWebView2CompositionController4"): + return webViewCompositionController.try_query() != nullptr; + default: + return false; + } + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Environment" }) && webViewEnv) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Environment"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment2"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment3"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment4"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment5"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment6"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment7"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment8"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment9"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment10"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment11"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment12"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment13"): + return webViewEnv.try_query() != nullptr; + case string_hash("ICoreWebView2Environment14"): + return webViewEnv.try_query() != nullptr; + default: + return false; + } + } + + return false; + } + + double InAppWebView::getZoomScale() const + { + return zoomScaleFactor_; + } + // flutter_view void InAppWebView::setSurfaceSize(size_t width, size_t height, float scale_factor) { @@ -1634,7 +2360,6 @@ namespace flutter_inappwebview_plugin } } - void InAppWebView::setPosition(size_t x, size_t y, float scale_factor) { if (!webViewController || !plugin || !plugin->registrar) { @@ -1665,7 +2390,6 @@ namespace flutter_inappwebview_plugin } } - void InAppWebView::setCursorPos(double x, double y) { if (!webViewCompositionController) { @@ -1751,35 +2475,66 @@ namespace flutter_inappwebview_plugin } } - void InAppWebView::setPointerButtonState(InAppWebViewPointerButton button, bool is_down) + void InAppWebView::setPointerButtonState(InAppWebViewPointerEventKind kind, InAppWebViewPointerButton button) { if (!webViewCompositionController) { return; } - COREWEBVIEW2_MOUSE_EVENT_KIND kind; - switch (button) { - case InAppWebViewPointerButton::Primary: - virtualKeys_.setIsLeftButtonDown(is_down); - kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN - : COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS eventVirtualKeys_ = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; + COREWEBVIEW2_MOUSE_EVENT_KIND eventKind; + UINT32 mouseData = 0; + POINT point = { 0, 0 };; + + switch (kind) { + case InAppWebViewPointerEventKind::Down: + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(true); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN; + break; + default: + eventKind = static_cast(0); + } + eventVirtualKeys_ = virtualKeys_.state(); + point = lastCursorPos_; break; - case InAppWebViewPointerButton::Secondary: - virtualKeys_.setIsRightButtonDown(is_down); - kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN - : COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + case InAppWebViewPointerEventKind::Up: + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(false); + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + break; + default: + eventKind = static_cast(0); + } + eventVirtualKeys_ = virtualKeys_.state(); + point = lastCursorPos_; break; - case InAppWebViewPointerButton::Tertiary: - virtualKeys_.setIsMiddleButtonDown(is_down); - kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN - : COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + case InAppWebViewPointerEventKind::Leave: + eventKind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEAVE; break; default: - kind = static_cast(0); + eventKind = static_cast(0); } - webViewCompositionController->SendMouseInput(kind, virtualKeys_.state(), 0, - lastCursorPos_); + webViewCompositionController->SendMouseInput(eventKind, eventVirtualKeys_, mouseData, point); } void InAppWebView::sendScroll(double delta, bool horizontal) @@ -1788,10 +2543,7 @@ namespace flutter_inappwebview_plugin return; } - // delta * 6 gives me a multiple of WHEEL_DELTA (120) - constexpr auto kScrollMultiplier = 6; - - auto offset = static_cast(delta * kScrollMultiplier); + auto offset = static_cast(delta * settings->scrollMultiplier); if (horizontal) { webViewCompositionController->SendMouseInput( @@ -1863,6 +2615,127 @@ namespace flutter_inappwebview_plugin return webErrorStatus >= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT && webErrorStatus <= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID; } + HRESULT InAppWebView::onCallJsHandler(const bool& isMainFrame, ICoreWebView2WebMessageReceivedEventArgs* args) + { + if (!channelDelegate) { + return S_OK; + } + + wil::unique_cotaskmem_string json; + if (succeededOrLog(args->get_WebMessageAsJson(&json))) { + nlohmann::basic_json<> message; + try { + message = nlohmann::json::parse(wide_to_utf8(json.get())); + } + catch (nlohmann::json::parse_error& ex) { + debugLog("Error parsing JSON message of callHandler method: " + std::string(ex.what())); + return S_OK; + } + + if (message.is_object() && message.contains("name") && message.at("name").is_string() && message.contains("body") && message.at("body").is_object()) { + auto name = message.at("name").get(); + auto body = message.at("body").get(); + + if (name.compare("callHandler") == 0) { + if (!body.contains("handlerName") || !body.at("handlerName").is_string()) { + debugLog("handlerName is null or undefined"); + return S_OK; + } + + auto handlerName = body.at("handlerName").get(); + auto bridgeSecret = body.contains("_bridgeSecret") && body.at("_bridgeSecret").is_string() ? body.at("_bridgeSecret").get() : ""; + auto callHandlerID = body.contains("_callHandlerID") && body.at("_callHandlerID").is_number_integer() ? body.at("_callHandlerID").get() : 0; + auto origin = body.contains("origin") && body.at("origin").is_string() ? body.at("origin").get() : ""; + auto requestUrl = body.contains("requestUrl") && body.at("requestUrl").is_string() ? body.at("requestUrl").get() : ""; + auto handlerArgs = body.contains("args") && body.at("args").is_string() ? body.at("args").get() : ""; + + wil::unique_cotaskmem_string sourceUrl; + if (succeededOrLog(args->get_Source(&sourceUrl))) { + requestUrl = wide_to_utf8(sourceUrl.get()); + origin = get_origin_from_url(requestUrl); + } + + if (!string_equals(expectedBridgeSecret, bridgeSecret)) { + debugLog("Bridge access attempt with wrong secret token, possibly from malicious code from origin: " + origin); + return S_OK; + } + + bool isOriginAllowed = false; + if (settings->javaScriptHandlersOriginAllowList.has_value()) { + for (auto& allowedOrigin : settings->javaScriptHandlersOriginAllowList.value()) { + if (std::regex_match(origin, std::regex(allowedOrigin))) { + isOriginAllowed = true; + break; + } + } + } + else { + // origin is by default allowed if the allow list is null + isOriginAllowed = true; + } + if (!isOriginAllowed) { + debugLog("Bridge access attempt from an origin not allowed: " + origin); + return S_OK; + } + + if (settings->javaScriptHandlersForMainFrameOnly && !isMainFrame) { + debugLog("Bridge access attempt from a sub-frame origin: " + origin); + return S_OK; + } + + /* + boolean isInternalHandler = true; + switch (handlerName) { + default: + isInternalHandler = false; + break; + } + + if (isInternalHandler) { + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].resolve(); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + return S_OK; + } + */ + + auto callback = std::make_unique(); + callback->defaultBehaviour = [this, callHandlerID](const std::optional response) + { + std::string json = "null"; + if (response.has_value() && !response.value()->IsNull()) { + json = std::get(*(response.value())); + } + + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].resolve(" + json + "); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + callback->error = [this, callHandlerID](const std::string& error_code, const std::string& error_message, const flutter::EncodableValue* error_details) + { + auto errorMessage = error_code + ", " + error_message; + debugLog(errorMessage); + + evaluateJavascript("if (window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "] != null) { \ + window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "].reject(new Error('" + replace_all_copy(errorMessage, "\'", "\\'") + "')); \ + delete window." + JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME() + "[" + std::to_string(callHandlerID) + "]; \ + }", ContentWorld::page(), nullptr); + }; + + auto data = std::make_unique(origin, requestUrl, isMainFrame, handlerArgs); + channelDelegate->onCallJsHandler(handlerName, std::move(data), std::move(callback)); + } + } + else { + debugLog("Invalid JSON message of callHandler method"); + } + } + + return S_OK; + } + InAppWebView::~InAppWebView() { debugLog("dealloc InAppWebView"); diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h index 6c3c3a73e..b7b27861c 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h @@ -16,6 +16,7 @@ #include "../types/ssl_certificate.h" #include "../types/url_request.h" #include "../types/web_history.h" +#include "../utils/uuid.h" #include "../webview_environment/webview_environment.h" #include "in_app_webview_settings.h" #include "user_content_controller.h" @@ -31,7 +32,7 @@ namespace flutter_inappwebview_plugin // custom_platform_view enum class InAppWebViewPointerButton { None, Primary, Secondary, Tertiary }; - enum class InAppWebViewPointerEventKind { Activate, Down, Enter, Leave, Up, Update }; + enum class InAppWebViewPointerEventKind { Activate, Down, Enter, Leave, Up, Update, Cancel }; typedef std::function SurfaceSizeChangedCallback; typedef std::function CursorChangedCallback; @@ -126,7 +127,7 @@ namespace flutter_inappwebview_plugin void setCursorPos(double x, double y); void setPointerUpdate(int32_t pointer, InAppWebViewPointerEventKind eventKind, double x, double y, double size, double pressure); - void setPointerButtonState(InAppWebViewPointerButton button, bool isDown); + void setPointerButtonState(InAppWebViewPointerEventKind kind, InAppWebViewPointerButton button); void sendScroll(double offset, bool horizontal); void setScrollDelta(double delta_x, double delta_y); void onSurfaceSizeChanged(SurfaceSizeChangedCallback callback) @@ -176,6 +177,9 @@ namespace flutter_inappwebview_plugin void pause() const; void resume() const; void getCertificate(const std::function>)> completionHandler) const; + void clearSslPreferences(const std::function completionHandler) const; + bool isInterfaceSupported(const std::string& interfaceName) const; + double getZoomScale() const; std::string pageFrameId() const { @@ -192,14 +196,19 @@ namespace flutter_inappwebview_plugin POINT lastCursorPos_ = { 0, 0 }; VirtualKeyState virtualKeys_; + const std::string expectedBridgeSecret = get_uuid(); + bool javaScriptBridgeEnabled = true; std::map> navigationActions_ = {}; std::shared_ptr lastNavigationAction_; bool isLoading_ = false; std::string pageFrameId_; std::map, EventRegistrationToken>> devToolsProtocolEventListener_ = {}; + int64_t previousAuthRequestFailureCount = 0; + double zoomScaleFactor_ = 1.0; void registerEventHandlers(); void registerSurfaceEventHandlers(); + HRESULT onCallJsHandler(const bool& isMainFrame, ICoreWebView2WebMessageReceivedEventArgs* args); }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp index 9b6850930..8a03dbd33 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp @@ -5,6 +5,7 @@ #include #include "../in_app_webview/in_app_webview_settings.h" +#include "../plugin_scripts_js/javascript_bridge_js.h" #include "../types/url_request.h" #include "../types/user_script.h" #include "../utils/flutter.h" @@ -18,29 +19,37 @@ namespace flutter_inappwebview_plugin { InAppWebViewManager::InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin) : plugin(plugin), - ChannelDelegate(plugin->registrar->messenger(), InAppWebViewManager::METHOD_CHANNEL_NAME), - rohelper_(std::make_unique(RO_INIT_SINGLETHREADED)) + ChannelDelegate(plugin->registrar->messenger(), InAppWebViewManager::METHOD_CHANNEL_NAME) { - if (rohelper_->WinRtAvailable()) { - DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), - DQTYPE_THREAD_CURRENT, DQTAT_COM_STA }; - - if (FAILED(rohelper_->CreateDispatcherQueueController( - options, dispatcher_queue_controller_.put()))) { - std::cerr << "Creating DispatcherQueueController failed." << std::endl; - return; - } + if (!rohelper_) { + rohelper_ = std::make_unique(RO_INIT_SINGLETHREADED); - if (!isGraphicsCaptureSessionSupported()) { - std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " - "supported." - << std::endl; - return; - } + if (rohelper_->WinRtAvailable()) { + DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, DQTAT_COM_STA }; + + if (FAILED(rohelper_->CreateDispatcherQueueController( + options, dispatcher_queue_controller_.put()))) { + std::cerr << "Creating DispatcherQueueController failed." << std::endl; + return; + } - graphics_context_ = std::make_unique(rohelper_.get()); - compositor_ = graphics_context_->CreateCompositor(); - valid_ = graphics_context_->IsValid(); + if (!isGraphicsCaptureSessionSupported()) { + std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " + "supported." + << std::endl; + return; + } + + graphics_context_ = std::make_unique(rohelper_.get()); + compositor_ = graphics_context_->CreateCompositor(); + if (compositor_) { + // fix for KernelBase.dll RaiseFailFastException + // when app is closing + compositor_->AddRef(); + } + valid_ = graphics_context_->IsValid(); + } } windowClass_.lpszClassName = CustomPlatformView::CLASS_NAME; @@ -66,6 +75,10 @@ namespace flutter_inappwebview_plugin else if (string_equals(methodName, "dispose")) { auto id = get_fl_map_value(*arguments, "id"); if (map_contains(webViews, (uint64_t)id)) { + auto platformView = webViews.at(id).get(); + if (platformView) { + platformView->UnregisterMethodCallHandler(); + } webViews.erase(id); } result->Success(); @@ -75,6 +88,14 @@ namespace flutter_inappwebview_plugin disposeKeepAlive(keepAliveId); result->Success(); } + else if (string_equals(methodName, "setJavaScriptBridgeName")) { + auto bridgeName = get_fl_map_value(*arguments, "bridgeName"); + JavaScriptBridgeJS::set_JAVASCRIPT_BRIDGE_NAME(bridgeName); + result->Success(); + } + else if (string_equals(methodName, "getJavaScriptBridgeName")) { + result->Success(JavaScriptBridgeJS::get_JAVASCRIPT_BRIDGE_NAME()); + } else { result->NotImplemented(); } @@ -84,6 +105,11 @@ namespace flutter_inappwebview_plugin { auto result_ = std::shared_ptr>(std::move(result)); + if (!plugin) { + result_->Error("0", "Cannot create the InAppWebView instance!"); + return; + } + auto settingsMap = get_fl_map_value(*arguments, "initialSettings"); auto urlRequestMap = get_optional_fl_map_value(*arguments, "initialUrlRequest"); auto initialFile = get_optional_fl_map_value(*arguments, "initialFile"); @@ -126,7 +152,7 @@ namespace flutter_inappwebview_plugin wil::com_ptr webViewController, wil::com_ptr webViewCompositionController) { - if (webViewEnv && webViewController && webViewCompositionController) { + if (plugin && webViewEnv && webViewController && webViewCompositionController) { std::optional>> initialUserScripts = initialUserScriptList.has_value() ? functional_map(initialUserScriptList.value(), [](const flutter::EncodableValue& map) { return std::make_shared(std::get(map)); }) : std::optional>>{}; @@ -186,6 +212,10 @@ namespace flutter_inappwebview_plugin void InAppWebViewManager::disposeKeepAlive(const std::string& keepAliveId) { if (map_contains(keepAliveWebViews, keepAliveId)) { + auto platformView = keepAliveWebViews.at(keepAliveId).get(); + if (platformView) { + platformView->UnregisterMethodCallHandler(); + } keepAliveWebViews.erase(keepAliveId); } } diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h index f48c799e7..b11a38074 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h @@ -52,13 +52,13 @@ namespace flutter_inappwebview_plugin void createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result); void disposeKeepAlive(const std::string& keepAliveId); private: - std::unique_ptr rohelper_; - winrt::com_ptr + inline static std::shared_ptr rohelper_ = nullptr; + inline static winrt::com_ptr dispatcher_queue_controller_; - std::unique_ptr graphics_context_; - winrt::com_ptr compositor_; + inline static std::unique_ptr graphics_context_ = nullptr; + inline static winrt::com_ptr compositor_; WNDCLASS windowClass_ = {}; - bool valid_ = false; + inline static bool valid_ = false; }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp index 387c919d1..7a2e5218d 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.cpp @@ -3,7 +3,6 @@ #include "in_app_webview.h" #include "in_app_webview_settings.h" -#include #include namespace flutter_inappwebview_plugin @@ -25,6 +24,33 @@ namespace flutter_inappwebview_plugin isInspectable = get_fl_map_value(encodableMap, "isInspectable", isInspectable); disableContextMenu = get_fl_map_value(encodableMap, "disableContextMenu", disableContextMenu); incognito = get_fl_map_value(encodableMap, "incognito", incognito); + if (fl_map_contains_not_null(encodableMap, "javaScriptHandlersOriginAllowList")) { + javaScriptHandlersOriginAllowList = get_optional_fl_map_value>(encodableMap, "javaScriptHandlersOriginAllowList"); + } + javaScriptHandlersForMainFrameOnly = get_fl_map_value(encodableMap, "javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly); + javaScriptBridgeEnabled = get_fl_map_value(encodableMap, "javaScriptBridgeEnabled", javaScriptBridgeEnabled); + if (fl_map_contains_not_null(encodableMap, "javaScriptBridgeOriginAllowList")) { + javaScriptBridgeOriginAllowList = get_optional_fl_map_value>(encodableMap, "javaScriptBridgeOriginAllowList"); + } + if (fl_map_contains_not_null(encodableMap, "javaScriptBridgeForMainFrameOnly")) { + javaScriptBridgeForMainFrameOnly = get_fl_map_value(encodableMap, "javaScriptBridgeForMainFrameOnly"); + } + if (fl_map_contains_not_null(encodableMap, "pluginScriptsOriginAllowList")) { + pluginScriptsOriginAllowList = get_optional_fl_map_value>(encodableMap, "pluginScriptsOriginAllowList"); + } + pluginScriptsForMainFrameOnly = get_fl_map_value(encodableMap, "pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly); + scrollMultiplier = get_fl_map_value(encodableMap, "scrollMultiplier", scrollMultiplier); + disableDefaultErrorPage = get_fl_map_value(encodableMap, "disableDefaultErrorPage", disableDefaultErrorPage); + statusBarEnabled = get_fl_map_value(encodableMap, "statusBarEnabled", statusBarEnabled); + browserAcceleratorKeysEnabled = get_fl_map_value(encodableMap, "browserAcceleratorKeysEnabled", browserAcceleratorKeysEnabled); + generalAutofillEnabled = get_fl_map_value(encodableMap, "generalAutofillEnabled", generalAutofillEnabled); + passwordAutosaveEnabled = get_fl_map_value(encodableMap, "passwordAutosaveEnabled", passwordAutosaveEnabled); + pinchZoomEnabled = get_fl_map_value(encodableMap, "pinchZoomEnabled", pinchZoomEnabled); + allowsBackForwardNavigationGestures = get_fl_map_value(encodableMap, "allowsBackForwardNavigationGestures", allowsBackForwardNavigationGestures); + hiddenPdfToolbarItems = get_fl_map_value(encodableMap, "hiddenPdfToolbarItems", hiddenPdfToolbarItems); + reputationCheckingRequired = get_fl_map_value(encodableMap, "reputationCheckingRequired", reputationCheckingRequired); + nonClientRegionSupportEnabled = get_fl_map_value(encodableMap, "nonClientRegionSupportEnabled", nonClientRegionSupportEnabled); + handleAcceleratorKeyPressed = get_fl_map_value(encodableMap, "handleAcceleratorKeyPressed", handleAcceleratorKeyPressed); } flutter::EncodableMap InAppWebViewSettings::toEncodableMap() const @@ -41,6 +67,25 @@ namespace flutter_inappwebview_plugin {"isInspectable", isInspectable}, {"disableContextMenu", disableContextMenu}, {"incognito", incognito}, + {"javaScriptHandlersOriginAllowList", make_fl_value(javaScriptHandlersOriginAllowList)}, + {"javaScriptHandlersForMainFrameOnly", javaScriptHandlersForMainFrameOnly}, + {"javaScriptBridgeEnabled", javaScriptBridgeEnabled}, + {"javaScriptBridgeOriginAllowList", make_fl_value(javaScriptBridgeOriginAllowList)}, + {"javaScriptBridgeForMainFrameOnly", make_fl_value(javaScriptBridgeForMainFrameOnly)}, + {"pluginScriptsOriginAllowList", make_fl_value(pluginScriptsOriginAllowList)}, + {"pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly}, + {"scrollMultiplier", scrollMultiplier}, + {"disableDefaultErrorPage", disableDefaultErrorPage}, + {"statusBarEnabled", statusBarEnabled}, + {"browserAcceleratorKeysEnabled", browserAcceleratorKeysEnabled}, + {"generalAutofillEnabled", generalAutofillEnabled}, + {"passwordAutosaveEnabled", passwordAutosaveEnabled}, + {"pinchZoomEnabled", pinchZoomEnabled}, + {"allowsBackForwardNavigationGestures", allowsBackForwardNavigationGestures}, + {"hiddenPdfToolbarItems", hiddenPdfToolbarItems}, + {"reputationCheckingRequired", reputationCheckingRequired}, + {"nonClientRegionSupportEnabled", nonClientRegionSupportEnabled}, + {"handleAcceleratorKeyPressed", handleAcceleratorKeyPressed} }; } @@ -67,14 +112,74 @@ namespace flutter_inappwebview_plugin if (SUCCEEDED(settings->get_AreDefaultContextMenusEnabled(&areDefaultContextMenusEnabled))) { settingsMap["disableContextMenu"] = !(bool)areDefaultContextMenusEnabled; } + BOOL isBuiltInErrorPageEnabled; + if (SUCCEEDED(settings->get_IsBuiltInErrorPageEnabled(&isBuiltInErrorPageEnabled))) { + settingsMap["disableDefaultErrorPage"] = !(bool)isBuiltInErrorPageEnabled; + } + BOOL isStatusBarEnabled; + if (SUCCEEDED(settings->get_IsBuiltInErrorPageEnabled(&isStatusBarEnabled))) { + settingsMap["statusBarEnabled"] = (bool)isStatusBarEnabled; + } - wil::com_ptr settings2; - if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings2)))) { + if (auto settings2 = settings.try_query()) { wil::unique_cotaskmem_string realUserAgent; if (SUCCEEDED(settings2->get_UserAgent(&realUserAgent))) { settingsMap["userAgent"] = wide_to_utf8(realUserAgent.get()); } } + + if (auto settings3 = settings.try_query()) { + BOOL areBrowserAcceleratorKeysEnabled; + if (SUCCEEDED(settings3->get_AreBrowserAcceleratorKeysEnabled(&areBrowserAcceleratorKeysEnabled))) { + settingsMap["browserAcceleratorKeysEnabled"] = (bool)areBrowserAcceleratorKeysEnabled; + } + } + + if (auto settings4 = settings.try_query()) { + BOOL isGeneralAutofillEnabled; + if (SUCCEEDED(settings4->get_IsGeneralAutofillEnabled(&isGeneralAutofillEnabled))) { + settingsMap["generalAutofillEnabled"] = (bool)isGeneralAutofillEnabled; + } + BOOL isPasswordAutosaveEnabled; + if (SUCCEEDED(settings4->get_IsPasswordAutosaveEnabled(&isPasswordAutosaveEnabled))) { + settingsMap["passwordAutosaveEnabled"] = (bool)isPasswordAutosaveEnabled; + } + } + + if (auto settings5 = settings.try_query()) { + BOOL isPinchZoomEnabled; + if (SUCCEEDED(settings5->get_IsPinchZoomEnabled(&isPinchZoomEnabled))) { + settingsMap["pinchZoomEnabled"] = (bool)isPinchZoomEnabled; + } + } + + if (auto settings6 = settings.try_query()) { + BOOL isSwipeNavigationEnabled; + if (SUCCEEDED(settings6->get_IsSwipeNavigationEnabled(&isSwipeNavigationEnabled))) { + settingsMap["allowsBackForwardNavigationGestures"] = (bool)isSwipeNavigationEnabled; + } + } + + if (auto settings7 = settings.try_query()) { + COREWEBVIEW2_PDF_TOOLBAR_ITEMS realHiddenPdfToolbarItems; + if (SUCCEEDED(settings7->get_HiddenPdfToolbarItems(&realHiddenPdfToolbarItems))) { + settingsMap["hiddenPdfToolbarItems"] = (int64_t)realHiddenPdfToolbarItems; + } + } + + if (auto settings8 = settings.try_query()) { + BOOL isReputationCheckingRequired; + if (SUCCEEDED(settings8->get_IsReputationCheckingRequired(&isReputationCheckingRequired))) { + settingsMap["reputationCheckingRequired"] = (bool)isReputationCheckingRequired; + } + } + + if (auto settings9 = settings.try_query()) { + BOOL isNonClientRegionSupportEnabled; + if (SUCCEEDED(settings9->get_IsNonClientRegionSupportEnabled(&isNonClientRegionSupportEnabled))) { + settingsMap["nonClientRegionSupportEnabled"] = (bool)isNonClientRegionSupportEnabled; + } + } } } return settingsMap; diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h index 92c9db08e..7eab9ea23 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_settings.h @@ -1,7 +1,9 @@ #ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ #define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_SETTINGS_H_ +#include #include +#include namespace flutter_inappwebview_plugin { @@ -21,6 +23,25 @@ namespace flutter_inappwebview_plugin bool isInspectable = true; bool disableContextMenu = false; bool incognito = false; + std::optional> javaScriptHandlersOriginAllowList = std::optional>{}; + bool javaScriptHandlersForMainFrameOnly = false; + bool javaScriptBridgeEnabled = true; + std::optional> javaScriptBridgeOriginAllowList = std::optional>{}; + std::optional javaScriptBridgeForMainFrameOnly = std::optional{}; + std::optional> pluginScriptsOriginAllowList = std::optional>{}; + bool pluginScriptsForMainFrameOnly = false; + int64_t scrollMultiplier = 1; + bool disableDefaultErrorPage = false; + bool statusBarEnabled = true; + bool browserAcceleratorKeysEnabled = true; + bool generalAutofillEnabled = true; + bool passwordAutosaveEnabled = false; + bool pinchZoomEnabled = true; + bool allowsBackForwardNavigationGestures = true; + int64_t hiddenPdfToolbarItems = COREWEBVIEW2_PDF_TOOLBAR_ITEMS::COREWEBVIEW2_PDF_TOOLBAR_ITEMS_NONE; + bool reputationCheckingRequired = true; + bool nonClientRegionSupportEnabled = false; + bool handleAcceleratorKeyPressed = false; InAppWebViewSettings(); InAppWebViewSettings(const flutter::EncodableMap& encodableMap); diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp index 9cbe2761f..35e7284c5 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.cpp @@ -343,11 +343,9 @@ namespace flutter_inappwebview_plugin std::string source = userScript->source; if (userScript->injectionTime == UserScriptInjectionTime::atDocumentEnd) { - source = replace_all_copy(USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE, VAR_PLACEHOLDER_VALUE, source); - std::ostringstream address; - address << std::addressof(userScript); - replace_all(source, VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE, address.str()); + source = "if (document.readyState === 'complete') { " + source + "} else { window.addEventListener('load', function() { " + source + " }); }"; } + source = UserContentController::wrapSourceCodeAddChecks(source, userScript); nlohmann::json parameters = { {"source", source} @@ -421,6 +419,44 @@ namespace flutter_inappwebview_plugin } } + std::string UserContentController::wrapSourceCodeAddChecks(const std::string& source, const std::shared_ptr userScript) + { + auto allowedOriginRules = userScript->allowedOriginRules; + auto forMainFrameOnly = userScript->forMainFrameOnly; + + std::string ifStatement = "if ("; + + if (allowedOriginRules.has_value() && !vector_contains(allowedOriginRules.value(), "*")) { + if (allowedOriginRules.value().empty()) { + // return empty source string if allowedOriginRules is an empty list. + // an empty list means that this UserScript is not allowed for any origin. + return ""; + } + + std::string jsRegExpArray = "["; + for (const auto& allowedOriginRule : allowedOriginRules.value()) { + if (jsRegExpArray.length() > 1) { + jsRegExpArray += ", "; + } + jsRegExpArray += "new RegExp('" + replace_all_copy(allowedOriginRule, "\'", "\\'") + "')"; + } + + if (jsRegExpArray.length() > 1) { + jsRegExpArray += "]"; + ifStatement += jsRegExpArray + ".some(function(rx) { return rx.test(window.location.origin); })"; + } + } + + if (forMainFrameOnly) { + if (ifStatement.length() > 4) { + ifStatement += " && "; + } + ifStatement += "window === window.top"; + } + + return ifStatement.length() > 4 ? ifStatement + ") { " + source + "}" : source; + } + UserContentController::~UserContentController() { debugLog("dealloc UserContentController"); diff --git a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h index a65929134..c5045c3ed 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/user_content_controller.h @@ -5,6 +5,7 @@ #include #include +#include "../plugin_scripts_js/javascript_bridge_js.h" #include "../plugin_scripts_js/javascript_bridge_js.h" #include "../plugin_scripts_js/plugin_scripts_util.h" #include "../types/content_world.h" @@ -15,13 +16,6 @@ namespace flutter_inappwebview_plugin { class InAppWebView; - const std::string USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE = "window.addEventListener('load', () => { \ - if (window." + JAVASCRIPT_BRIDGE_NAME + " != null && (window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded == null || !window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded)) { \ - window." + JAVASCRIPT_BRIDGE_NAME + "._userScript" + VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE + "AtDocumentEndLoaded = true; \ - " + VAR_PLACEHOLDER_VALUE + " \ - } \ - });"; - class UserContentController { public: @@ -51,6 +45,7 @@ namespace flutter_inappwebview_plugin void registerEventHandlers(); void createContentWorld(const std::shared_ptr contentWorld, const std::function completionHandler); + private: InAppWebView* webView_; @@ -73,6 +68,8 @@ namespace flutter_inappwebview_plugin void removeScriptFromWebView(std::shared_ptr userScript, const std::function completionHandler) const; void addPluginScriptsIfRequired(const std::shared_ptr contentWorld); + + static std::string wrapSourceCodeAddChecks(const std::string& source, const std::shared_ptr userScript); }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_USER_CONTENT_CONTROLLER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp index df6a4bc8a..c09a23689 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp @@ -54,7 +54,7 @@ namespace flutter_inappwebview_plugin { decodeResult = [](const flutter::EncodableValue* value) { - return std::make_shared(std::get(*value)); + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); }; } @@ -74,6 +74,38 @@ namespace flutter_inappwebview_plugin }; } + WebViewChannelDelegate::ReceivedHttpAuthRequestCallback::ReceivedHttpAuthRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ReceivedClientCertRequestCallback::ReceivedClientCertRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::ReceivedServerTrustAuthRequestCallback::ReceivedServerTrustAuthRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + + WebViewChannelDelegate::DownloadStartRequestCallback::DownloadStartRequestCallback() + { + decodeResult = [](const flutter::EncodableValue* value) + { + return value == nullptr || value->IsNull() ? std::optional>{} : std::make_shared(std::get(*value)); + }; + } + void WebViewChannelDelegate::HandleMethodCall(const flutter::MethodCall& method_call, std::unique_ptr> result) { @@ -269,6 +301,20 @@ namespace flutter_inappwebview_plugin result_->Success(data.has_value() ? data.value()->toEncodableMap() : make_fl_value()); }); } + else if (string_equals(methodName, "clearSslPreferences")) { + auto result_ = std::shared_ptr>(std::move(result)); + webView->clearSslPreferences([result_ = std::move(result_)]() + { + result_->Success(); + }); + } + else if (string_equals(methodName, "isInterfaceSupported")) { + auto interfaceName = get_fl_map_value(arguments, "interface"); + result->Success(webView->isInterfaceSupported(interfaceName)); + } + else if (string_equals(methodName, "getZoomScale")) { + result->Success(webView->getZoomScale()); + } // for inAppBrowser else if (webView->inAppBrowser && string_equals(methodName, "show")) { webView->inAppBrowser->show(); @@ -377,7 +423,7 @@ namespace flutter_inappwebview_plugin channel->InvokeMethod("onUpdateVisitedHistory", std::move(arguments)); } - void WebViewChannelDelegate::onCallJsHandler(const std::string& handlerName, const std::string& args, std::unique_ptr callback) const + void WebViewChannelDelegate::onCallJsHandler(const std::string& handlerName, const std::unique_ptr data, std::unique_ptr callback) const { if (!channel) { callback->defaultBehaviour(std::nullopt); @@ -386,7 +432,7 @@ namespace flutter_inappwebview_plugin auto arguments = std::make_unique(flutter::EncodableMap{ {"handlerName", handlerName}, - {"args", args} + {"data", data->toEncodableMap()} }); channel->InvokeMethod("onCallJsHandler", std::move(arguments), std::move(callback)); } @@ -488,6 +534,114 @@ namespace flutter_inappwebview_plugin channel->InvokeMethod("onLoadResourceWithCustomScheme", std::move(arguments), std::move(callback)); } + void WebViewChannelDelegate::onReceivedHttpAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedHttpAuthRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedClientCertRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedClientCertRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onReceivedServerTrustAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(challenge->toEncodableMap()); + channel->InvokeMethod("onReceivedServerTrustAuthRequest", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onRenderProcessGone(const std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onDevToolsProtocolEventReceived", std::move(arguments)); + } + + void WebViewChannelDelegate::onRenderProcessUnresponsive(const std::optional& url) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"url", make_fl_value(url)}, + }); + channel->InvokeMethod("onRenderProcessUnresponsive", std::move(arguments)); + } + void WebViewChannelDelegate::onWebContentProcessDidTerminate() const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(); + channel->InvokeMethod("onWebContentProcessDidTerminate", std::move(arguments)); + } + + void WebViewChannelDelegate::onProcessFailed(const std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onProcessFailed", std::move(arguments)); + } + + void WebViewChannelDelegate::onDownloadStarting(std::shared_ptr request, std::unique_ptr callback) const + { + if (!channel) { + callback->defaultBehaviour(std::nullopt); + return; + } + + auto arguments = std::make_unique(request->toEncodableMap()); + channel->InvokeMethod("onDownloadStarting", std::move(arguments), std::move(callback)); + } + + void WebViewChannelDelegate::onAcceleratorKeyPressed(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onAcceleratorKeyPressed", std::move(arguments)); + } + + void WebViewChannelDelegate::onZoomScaleChanged(const double& oldScale, const double& newScale) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"oldScale", make_fl_value(oldScale)}, + {"newScale", make_fl_value(newScale)}, + }); + channel->InvokeMethod("onZoomScaleChanged", std::move(arguments)); + } + WebViewChannelDelegate::~WebViewChannelDelegate() { debugLog("dealloc WebViewChannelDelegate"); diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h index 005e782dd..3421a9fbe 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h @@ -1,15 +1,26 @@ #ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ #define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_CHANNEL_DELEGATE_H_ -#include #include +#include "../types/accelerator_key_pressed_detail.h" #include "../types/base_callback_result.h" #include "../types/channel_delegate.h" +#include "../types/client_cert_challenge.h" +#include "../types/client_cert_response.h" #include "../types/create_window_action.h" #include "../types/custom_scheme_response.h" +#include "../types/download_start_request.h" +#include "../types/download_start_response.h" +#include "../types/http_auth_response.h" +#include "../types/http_authentication_challenge.h" +#include "../types/javascript_handler_function_data.h" #include "../types/navigation_action.h" #include "../types/permission_response.h" +#include "../types/process_failed_detail.h" +#include "../types/render_process_gone_detail.h" +#include "../types/server_trust_auth_response.h" +#include "../types/server_trust_challenge.h" #include "../types/web_resource_error.h" #include "../types/web_resource_request.h" #include "../types/web_resource_response.h" @@ -18,7 +29,7 @@ namespace flutter_inappwebview_plugin { class InAppWebView; - enum NavigationActionPolicy { cancel = 0, allow = 1 }; + enum class NavigationActionPolicy { cancel = 0, allow = 1 }; class WebViewChannelDelegate : public ChannelDelegate { @@ -61,6 +72,30 @@ namespace flutter_inappwebview_plugin ~LoadResourceWithCustomSchemeCallback() = default; }; + class ReceivedHttpAuthRequestCallback : public BaseCallbackResult> { + public: + ReceivedHttpAuthRequestCallback(); + ~ReceivedHttpAuthRequestCallback() = default; + }; + + class ReceivedClientCertRequestCallback : public BaseCallbackResult> { + public: + ReceivedClientCertRequestCallback(); + ~ReceivedClientCertRequestCallback() = default; + }; + + class ReceivedServerTrustAuthRequestCallback : public BaseCallbackResult> { + public: + ReceivedServerTrustAuthRequestCallback(); + ~ReceivedServerTrustAuthRequestCallback() = default; + }; + + class DownloadStartRequestCallback : public BaseCallbackResult> { + public: + DownloadStartRequestCallback(); + ~DownloadStartRequestCallback() = default; + }; + WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger); WebViewChannelDelegate(InAppWebView* webView, flutter::BinaryMessenger* messenger, const std::string& name); ~WebViewChannelDelegate(); @@ -77,7 +112,7 @@ namespace flutter_inappwebview_plugin void onReceivedHttpError(std::shared_ptr request, std::shared_ptr error) const; void onTitleChanged(const std::optional& title) const; void onUpdateVisitedHistory(const std::optional& url, const std::optional& isReload) const; - void onCallJsHandler(const std::string& handlerName, const std::string& args, std::unique_ptr callback) const; + void onCallJsHandler(const std::string& handlerName, const std::unique_ptr data, std::unique_ptr callback) const; void onConsoleMessage(const std::string& message, const int64_t& messageLevel) const; void onDevToolsProtocolEventReceived(const std::string& eventName, const std::string& data) const; void onCreateWindow(std::shared_ptr createWindowAction, std::unique_ptr callback) const; @@ -85,6 +120,16 @@ namespace flutter_inappwebview_plugin void onPermissionRequest(const std::string& origin, const std::vector& resources, std::unique_ptr callback) const; void shouldInterceptRequest(std::shared_ptr request, std::unique_ptr callback) const; void onLoadResourceWithCustomScheme(std::shared_ptr request, std::unique_ptr callback) const; + void onReceivedHttpAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onReceivedClientCertRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onReceivedServerTrustAuthRequest(std::shared_ptr challenge, std::unique_ptr callback) const; + void onRenderProcessGone(const std::shared_ptr detail) const; + void onRenderProcessUnresponsive(const std::optional& url) const; + void onWebContentProcessDidTerminate() const; + void onProcessFailed(const std::shared_ptr detail) const; + void onDownloadStarting(std::shared_ptr request, std::unique_ptr callback) const; + void onAcceleratorKeyPressed(std::shared_ptr detail) const; + void onZoomScaleChanged(const double& oldScale, const double& newScale) const; }; } diff --git a/flutter_inappwebview_windows/windows/platform_util.cpp b/flutter_inappwebview_windows/windows/platform_util.cpp new file mode 100644 index 000000000..4de817672 --- /dev/null +++ b/flutter_inappwebview_windows/windows/platform_util.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +#include "in_app_webview/in_app_webview_manager.h" +#include "platform_util.h" +#include "types/callbacks_complete.h" +#include "utils/flutter.h" +#include "utils/log.h" + +namespace flutter_inappwebview_plugin +{ + using namespace Microsoft::WRL; + + PlatformUtil::PlatformUtil(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), ChannelDelegate(plugin->registrar->messenger(), PlatformUtil::METHOD_CHANNEL_NAME_PREFIX) + {} + + void PlatformUtil::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + result->NotImplemented(); + } + + void PlatformUtil::_EmitEvent(std::string eventName) + { + if (channel == nullptr) + return; + flutter::EncodableMap args = flutter::EncodableMap(); + args[flutter::EncodableValue("eventName")] = flutter::EncodableValue(eventName); + channel->InvokeMethod( + "onEvent", std::make_unique(args)); + } + + std::optional PlatformUtil::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) noexcept + { + std::optional result = std::nullopt; + + if (message == WM_MOVING) { + window_is_moving_ = true; + if (!window_start_move_sent_) { + window_start_move_sent_ = true; + _EmitEvent("onWindowStartMove"); + } + _EmitEvent("onWindowMove"); + } + else if (message == WM_EXITSIZEMOVE) { + if (window_is_moving_) { + window_is_moving_ = false; + window_start_move_sent_ = false; + _EmitEvent("onWindowEndMove"); + } + } + + return result; + } + + PlatformUtil::~PlatformUtil() + { + debugLog("dealloc PlatformUtil"); + plugin = nullptr; + } +} diff --git a/flutter_inappwebview_windows/windows/platform_util.h b/flutter_inappwebview_windows/windows/platform_util.h new file mode 100644 index 000000000..75b5d795b --- /dev/null +++ b/flutter_inappwebview_windows/windows/platform_util.h @@ -0,0 +1,41 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ + +#include +#include +#include +#include + +#include "flutter_inappwebview_windows_plugin.h" +#include "types/channel_delegate.h" + +namespace flutter_inappwebview_plugin +{ + class PlatformUtil : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_platformutil"; + + const FlutterInappwebviewWindowsPlugin* plugin; + + PlatformUtil(const FlutterInappwebviewWindowsPlugin* plugin); + ~PlatformUtil(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + std::optional PlatformUtil::HandleWindowProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) noexcept; + + private: + void PlatformUtil::_EmitEvent(std::string eventName); + bool window_is_moving_ = false; + bool window_start_move_sent_ = false; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PLATFORM_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp deleted file mode 100644 index dcb743bf8..000000000 --- a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include "javascript_bridge_js.h" - -namespace flutter_inappwebview_plugin -{ - std::unique_ptr createJavaScriptBridgePluginScript() - { - const std::vector allowedOriginRules = { "*" }; - return std::make_unique( - JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, - JAVASCRIPT_BRIDGE_JS_SOURCE, - UserScriptInjectionTime::atDocumentStart, - allowedOriginRules, - nullptr, - true - ); - } -} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h index a10c832c4..2c78a75e6 100644 --- a/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/javascript_bridge_js.h @@ -5,27 +5,106 @@ #include #include "../types/plugin_script.h" +#include "../utils/log.h" +#include "../utils/string.h" namespace flutter_inappwebview_plugin { - const std::string JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; - const std::string JAVASCRIPT_BRIDGE_JS_SOURCE = "window." + JAVASCRIPT_BRIDGE_NAME + " = {}; \ - window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() { \ - var _callHandlerID = setTimeout(function() {}); \ - window.chrome.webview.postMessage({ 'name': 'callHandler', 'body': {'handlerName': arguments[0], '_callHandlerID' : _callHandlerID, 'args' : JSON.stringify(Array.prototype.slice.call(arguments, 1))} }); \ - return new Promise(function(resolve, reject) { \ - window." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = { resolve: resolve, reject : reject };\ - });\ - };"; - const std::string JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; - const std::string PLATFORM_READY_JS_SOURCE = "(function() { \ - if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + " != null && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) { \ - window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady')); \ - window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady = true; \ - } \ - })();"; - - std::unique_ptr createJavaScriptBridgePluginScript(); + + class JavaScriptBridgeJS + { + public: + static void set_JAVASCRIPT_BRIDGE_NAME(const std::string& bridgeName) + { + _JAVASCRIPT_BRIDGE_NAME = bridgeName; + } + + static std::string get_JAVASCRIPT_BRIDGE_NAME() + { + return _JAVASCRIPT_BRIDGE_NAME; + } + + inline static const std::string JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"; + + inline static const std::string VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET = "$IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_BRIDGE_SECRET"; + + static std::string JAVASCRIPT_BRIDGE_JS_SOURCE() + { + return "window." + get_JAVASCRIPT_BRIDGE_NAME() + " = {}; \ + (function(window) {\ + var bridgeSecret = '" + VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET + "';\ + var origin = '';\ + var requestUrl = '';\ + var isMainFrame = false;\ + var _JSON_stringify;\ + var _Array_slice;\ + var _setTimeout;\ + var _Promise;\ + var _postMessage;\ + try {\ + origin = window.location.origin;\ + } catch (_) {}\ + try {\ + requestUrl = window.location.href;\ + } catch (_) {}\ + try {\ + isMainFrame = window === window.top;\ + } catch (_) {}\ + try {\ + _JSON_stringify = window.JSON.stringify;\ + _Array_slice = window.Array.prototype.slice;\ + _Array_slice.call = window.Function.prototype.call;\ + _setTimeout = window.setTimeout;\ + _Promise = window.Promise;\ + _postMessage = window.chrome.webview.postMessage;\ + } catch (_) { return; }\ + window." + get_JAVASCRIPT_BRIDGE_NAME() + ".callHandler = function() { \ + var _callHandlerID = _setTimeout(function() {}); \ + _postMessage({ 'name': 'callHandler', 'body': {\ + 'handlerName': arguments[0],\ + '_callHandlerID' : _callHandlerID,\ + '_bridgeSecret': bridgeSecret,\ + 'origin': origin,\ + 'requestUrl': requestUrl,\ + 'args' : _JSON_stringify(_Array_slice.call(arguments, 1))}\ + });\ + return new _Promise(function(resolve, reject) { \ + try {\ + (isMainFrame ? window : window.top)." + get_JAVASCRIPT_BRIDGE_NAME() + "[_callHandlerID] = { resolve: resolve, reject : reject };\ + } catch(e) { resolve(); }\ + });\ + };\ + })(window);"; + } + + static std::string PLATFORM_READY_JS_SOURCE() + { + return "(function() { \ + if ((window.top == null || window.top === window) && window." + get_JAVASCRIPT_BRIDGE_NAME() + " != null && window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady == null) { \ + window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady')); \ + window." + get_JAVASCRIPT_BRIDGE_NAME() + "._platformReady = true; \ + } \ + })();"; + } + + static std::unique_ptr JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT(const std::string& expectedBridgeSecret, + const std::optional>& allowedOriginRules, const bool forMainFrameOnly) + { + auto source = replace_all_copy(JAVASCRIPT_BRIDGE_JS_SOURCE(), VAR_JAVASCRIPT_BRIDGE_BRIDGE_SECRET, expectedBridgeSecret); + return std::make_unique( + JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME, + source, + UserScriptInjectionTime::atDocumentStart, + forMainFrameOnly, + allowedOriginRules, + nullptr, + true + ); + } + + private: + inline static std::string _JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"; + }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_BRIDGE_JS_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h index 239783c3b..c5990505f 100644 --- a/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h +++ b/flutter_inappwebview_windows/windows/plugin_scripts_js/plugin_scripts_util.h @@ -6,7 +6,6 @@ namespace flutter_inappwebview_plugin { const std::string VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"; - const std::string VAR_PLACEHOLDER_MEMORY_ADDRESS_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_MEMORY_ADDRESS_VALUE"; const std::string VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES"; const std::string VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES"; const std::string VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY"; diff --git a/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp new file mode 100644 index 000000000..dcb8da55c --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.cpp @@ -0,0 +1,34 @@ +#include "../utils/flutter.h" +#include "accelerator_key_pressed_detail.h" + +namespace flutter_inappwebview_plugin +{ + AcceleratorKeyPressedDetail::AcceleratorKeyPressedDetail(const std::optional& keyEventKind, + const std::optional> physicalKeyStatus, + const std::optional& virtualKey) + : keyEventKind(keyEventKind), physicalKeyStatus(physicalKeyStatus), virtualKey(virtualKey) + {} + + std::unique_ptr AcceleratorKeyPressedDetail::fromICoreWebView2AcceleratorKeyPressedEventArgs(const wil::com_ptr args) + { + COREWEBVIEW2_KEY_EVENT_KIND kind; + std::optional keyEventKind = SUCCEEDED(args->get_KeyEventKind(&kind)) ? (int64_t)kind : std::optional{}; + + COREWEBVIEW2_PHYSICAL_KEY_STATUS status; + std::optional> physicalKeyStatus = SUCCEEDED(args->get_PhysicalKeyStatus(&status)) ? PhysicalKeyStatus::fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(status) : std::optional>{}; + + UINT vKey; + std::optional virtualKey = SUCCEEDED(args->get_VirtualKey(&vKey)) ? (int64_t)vKey : std::optional{}; + + return std::make_unique(keyEventKind, physicalKeyStatus, virtualKey); + } + + flutter::EncodableMap AcceleratorKeyPressedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + { "keyEventKind", make_fl_value(keyEventKind) }, + { "physicalKeyStatus", physicalKeyStatus.has_value() ? make_fl_value(physicalKeyStatus.value()->toEncodableMap()) : make_fl_value() }, + { "virtualKey", make_fl_value(virtualKey) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h new file mode 100644 index 000000000..acf33a28f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/accelerator_key_pressed_detail.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H +#define FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H + +#include +#include +#include +#include + +#include "physical_key_status.h" + +namespace flutter_inappwebview_plugin +{ + class AcceleratorKeyPressedDetail + { + public: + const std::optional keyEventKind; + const std::optional> physicalKeyStatus; + const std::optional virtualKey; + + AcceleratorKeyPressedDetail(const std::optional& keyEventKind, + const std::optional> physicalKeyStatus, + const std::optional& virtualKey); + ~AcceleratorKeyPressedDetail() = default; + + static std::unique_ptr fromICoreWebView2AcceleratorKeyPressedEventArgs(const wil::com_ptr args); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_ACCELERATOR_KEY_PRESSED_DETAIL_H \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp new file mode 100644 index 000000000..f92c58ea5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.cpp @@ -0,0 +1,17 @@ +#include "../utils/flutter.h" +#include "browser_process_exited_detail.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessExitedDetail::BrowserProcessExitedDetail(const std::optional& kind, const std::optional& processId) + : kind(kind), processId(processId) + {} + + flutter::EncodableMap BrowserProcessExitedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"processId", make_fl_value(processId)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h new file mode 100644 index 000000000..158a92785 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_exited_detail.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessExitedDetail + { + public: + const std::optional kind; + const std::optional processId; + + BrowserProcessExitedDetail(const std::optional& kind, + const std::optional& processId); + ~BrowserProcessExitedDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_EXITED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_info.cpp b/flutter_inappwebview_windows/windows/types/browser_process_info.cpp new file mode 100644 index 000000000..dd78fe414 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_info.cpp @@ -0,0 +1,62 @@ +#include "../utils/flutter.h" +#include "../utils/vector.h" +#include "browser_process_info.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessInfo::BrowserProcessInfo(const std::optional& kind, const std::optional& processId, const std::optional>>& frameInfos) + : kind(kind), processId(processId), frameInfos(frameInfos) + {} + + std::unique_ptr BrowserProcessInfo::fromICoreWebView2ProcessInfo(const wil::com_ptr processInfo) + { + COREWEBVIEW2_PROCESS_KIND processKind; + std::optional kind = SUCCEEDED(processInfo->get_Kind(&processKind)) ? static_cast(processKind) : std::optional{}; + + INT32 pid; + std::optional processId = SUCCEEDED(processInfo->get_ProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + const std::optional>> frameInfos = std::optional>>{}; + + return std::make_unique(kind, processId, frameInfos); + } + + std::unique_ptr BrowserProcessInfo::fromICoreWebView2ProcessExtendedInfo(const wil::com_ptr processExtendedInfo) + { + wil::com_ptr processInfo; + processExtendedInfo->get_ProcessInfo(&processInfo); + + COREWEBVIEW2_PROCESS_KIND processKind; + std::optional kind = processInfo && SUCCEEDED(processInfo->get_Kind(&processKind)) ? static_cast(processKind) : std::optional{}; + + INT32 pid; + std::optional processId = processInfo && SUCCEEDED(processInfo->get_ProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + std::optional>> frameInfos = std::optional>>{}; + wil::com_ptr frameInfoCollection; + if (SUCCEEDED(processExtendedInfo->get_AssociatedFrameInfos(&frameInfoCollection))) { + wil::com_ptr iterator; + if (SUCCEEDED(frameInfoCollection->GetIterator(&iterator))) { + frameInfos = std::vector>{}; + BOOL hasCurrent = FALSE; + if (SUCCEEDED(iterator->get_HasCurrent(&hasCurrent)) && hasCurrent) { + wil::com_ptr frameInfo; + if (SUCCEEDED(iterator->GetCurrent(&frameInfo))) { + frameInfos.value().push_back(FrameInfo::fromICoreWebView2FrameInfo(frameInfo)); + } + } + } + } + + return std::make_unique(kind, processId, frameInfos); + } + + flutter::EncodableMap BrowserProcessInfo::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"processId", make_fl_value(processId)}, + {"frameInfos", frameInfos.has_value() ? make_fl_value(functional_map(frameInfos.value(), [](const std::shared_ptr& frameInfo) { return frameInfo->toEncodableMap(); })) : make_fl_value()} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_info.h b/flutter_inappwebview_windows/windows/types/browser_process_info.h new file mode 100644 index 000000000..56e20e31f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_info.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ + +#include +#include +#include +#include + +#include "../types/frame_info.h" + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessInfo + { + public: + const std::optional kind; + const std::optional processId; + const std::optional>> frameInfos; + + BrowserProcessInfo(const std::optional& kind, + const std::optional& processId, + const std::optional>>& frameInfos); + ~BrowserProcessInfo() = default; + + static std::unique_ptr fromICoreWebView2ProcessInfo(const wil::com_ptr processInfo); + static std::unique_ptr fromICoreWebView2ProcessExtendedInfo(const wil::com_ptr processExtendedInfo); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFO_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp new file mode 100644 index 000000000..e6e3a9210 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.cpp @@ -0,0 +1,47 @@ +#include "../utils/flutter.h" +#include "../utils/vector.h" +#include "browser_process_infos_changed_detail.h" + +namespace flutter_inappwebview_plugin +{ + BrowserProcessInfosChangedDetail::BrowserProcessInfosChangedDetail(const std::vector>& infos) + : infos(infos) + {} + + std::unique_ptr BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(const wil::com_ptr processCollection) + { + std::vector> infos = {}; + UINT count; + if (SUCCEEDED(processCollection->get_Count(&count))) { + for (UINT i = 0; i < count; i++) { + wil::com_ptr processInfo; + if (SUCCEEDED(processCollection->GetValueAtIndex(i, &processInfo))) { + infos.push_back(BrowserProcessInfo::fromICoreWebView2ProcessInfo(processInfo)); + } + } + } + return std::make_unique(infos); + } + + std::unique_ptr BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(const wil::com_ptr processCollection) + { + std::vector> infos = {}; + UINT count; + if (SUCCEEDED(processCollection->get_Count(&count))) { + for (UINT i = 0; i < count; i++) { + wil::com_ptr processInfo; + if (SUCCEEDED(processCollection->GetValueAtIndex(i, &processInfo))) { + infos.push_back(BrowserProcessInfo::fromICoreWebView2ProcessExtendedInfo(processInfo)); + } + } + } + return std::make_unique(infos); + } + + flutter::EncodableMap BrowserProcessInfosChangedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + { "infos", make_fl_value(functional_map(infos, [](const std::shared_ptr& info) { return info->toEncodableMap(); })) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h new file mode 100644 index 000000000..225a552b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/browser_process_infos_changed_detail.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ + +#include + +#include "browser_process_info.h" + +namespace flutter_inappwebview_plugin +{ + class BrowserProcessInfosChangedDetail + { + public: + const std::vector> infos; + + BrowserProcessInfosChangedDetail(const std::vector>& infos); + ~BrowserProcessInfosChangedDetail() = default; + + static std::unique_ptr fromICoreWebView2ProcessInfoCollection(const wil::com_ptr processCollection); + static std::unique_ptr fromICoreWebView2ProcessExtendedInfoCollection(const wil::com_ptr processCollection); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_BROWSER_PROCESS_INFOS_CHANGED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.cpp b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp index 983a2366f..b5db15048 100644 --- a/flutter_inappwebview_windows/windows/types/channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.cpp @@ -24,12 +24,16 @@ namespace flutter_inappwebview_plugin std::unique_ptr> result) {} - ChannelDelegate::~ChannelDelegate() + void ChannelDelegate::UnregisterMethodCallHandler() const { - messenger = nullptr; - if (channel != nullptr) { + if (channel) { channel->SetMethodCallHandler(nullptr); } + } + + ChannelDelegate::~ChannelDelegate() + { + messenger = nullptr; channel.reset(); } } \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/channel_delegate.h b/flutter_inappwebview_windows/windows/types/channel_delegate.h index a412b7d10..5ccfdfa12 100644 --- a/flutter_inappwebview_windows/windows/types/channel_delegate.h +++ b/flutter_inappwebview_windows/windows/types/channel_delegate.h @@ -19,6 +19,8 @@ namespace flutter_inappwebview_plugin virtual void HandleMethodCall( const flutter::MethodCall& method_call, std::unique_ptr> result); + + void UnregisterMethodCallHandler() const; }; } diff --git a/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp b/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp new file mode 100644 index 000000000..f8359a8ee --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_challenge.cpp @@ -0,0 +1,21 @@ +#include "../utils/flutter.h" +#include "client_cert_challenge.h" + +namespace flutter_inappwebview_plugin +{ + ClientCertChallenge::ClientCertChallenge(const std::shared_ptr protectionSpace, + const std::vector& allowedCertificateAuthorities, + const bool& isProxy, + const std::vector>& mutuallyTrustedCertificates) + : URLAuthenticationChallenge(protectionSpace), allowedCertificateAuthorities(allowedCertificateAuthorities), isProxy(isProxy), mutuallyTrustedCertificates(mutuallyTrustedCertificates) + {} + + flutter::EncodableMap ClientCertChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + map.insert({ "allowedCertificateAuthorities", make_fl_value(allowedCertificateAuthorities) }); + map.insert({ "isProxy", make_fl_value(isProxy) }); + map.insert({ "mutuallyTrustedCertificates", make_fl_value(functional_map(mutuallyTrustedCertificates, [](const std::shared_ptr& item) { return item->toEncodableMap(); })) }); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_challenge.h b/flutter_inappwebview_windows/windows/types/client_cert_challenge.h new file mode 100644 index 000000000..4f3a4c0ac --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_challenge.h @@ -0,0 +1,29 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ + +#include +#include +#include + +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + class ClientCertChallenge : URLAuthenticationChallenge + { + public: + const std::vector allowedCertificateAuthorities; + const bool isProxy; + const std::vector> mutuallyTrustedCertificates; + + ClientCertChallenge(const std::shared_ptr protectionSpace, + const std::vector& allowedCertificateAuthorities, + const bool& isProxy, + const std::vector>& mutuallyTrustedCertificates); + ~ClientCertChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_response.cpp b/flutter_inappwebview_windows/windows/types/client_cert_response.cpp new file mode 100644 index 000000000..ab33f5491 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_response.cpp @@ -0,0 +1,23 @@ +#include "../utils/flutter.h" +#include "client_cert_response.h" + +namespace flutter_inappwebview_plugin +{ + ClientCertResponse::ClientCertResponse(const int64_t& selectedCertificate, + const std::optional& action) + : selectedCertificate(selectedCertificate), action(action) + {} + + ClientCertResponse::ClientCertResponse(const flutter::EncodableMap& map) + : ClientCertResponse(get_fl_map_value(map, "selectedCertificate"), + ClientCertResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap ClientCertResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"selectedCertificate", make_fl_value(selectedCertificate)}, + {"action", make_fl_value(ClientCertResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/client_cert_response.h b/flutter_inappwebview_windows/windows/types/client_cert_response.h new file mode 100644 index 000000000..bdd9586fd --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/client_cert_response.h @@ -0,0 +1,63 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class ClientCertResponseAction { + cancel = 0, + proceed, + ignore + }; + + inline ClientCertResponseAction ClientCertResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return ClientCertResponseAction::cancel; + } + switch (action.value()) { + case 0: + return ClientCertResponseAction::cancel; + case 1: + return ClientCertResponseAction::proceed; + case 2: + return ClientCertResponseAction::ignore; + default: + return ClientCertResponseAction::cancel; + } + } + + inline std::optional ClientCertResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class ClientCertResponse + { + public: + const int64_t selectedCertificate; + const std::optional action; + + ClientCertResponse(const int64_t& selectedCertificate, + const std::optional& action); + ClientCertResponse(const flutter::EncodableMap& map); + ~ClientCertResponse() = default; + + bool ClientCertResponse::operator==(const ClientCertResponse& other) + { + return selectedCertificate == other.selectedCertificate && + action == other.action; + } + bool ClientCertResponse::operator!=(const ClientCertResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_CLIENT_CERT_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/content_world.cpp b/flutter_inappwebview_windows/windows/types/content_world.cpp index a95e773cf..1c9ec1fee 100644 --- a/flutter_inappwebview_windows/windows/types/content_world.cpp +++ b/flutter_inappwebview_windows/windows/types/content_world.cpp @@ -12,7 +12,7 @@ namespace flutter_inappwebview_plugin {} ContentWorld::ContentWorld(const flutter::EncodableMap& map) - : name(get_fl_map_value(map, "name")) + : ContentWorld(get_fl_map_value(map, "name")) {} bool ContentWorld::isSame(const ContentWorld& contentWorld) const diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp index b3cd295cf..1a25945b3 100644 --- a/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_registration.cpp @@ -10,10 +10,10 @@ namespace flutter_inappwebview_plugin {} CustomSchemeRegistration::CustomSchemeRegistration(const flutter::EncodableMap& map) - : scheme(get_fl_map_value(map, "scheme")), - allowedOrigins(get_optional_fl_map_value>(map, "allowedOrigins")), - treatAsSecure(get_optional_fl_map_value(map, "treatAsSecure")), - hasAuthorityComponent(get_optional_fl_map_value(map, "hasAuthorityComponent")) + : CustomSchemeRegistration(get_fl_map_value(map, "scheme"), + get_optional_fl_map_value>(map, "allowedOrigins"), + get_optional_fl_map_value(map, "treatAsSecure"), + get_optional_fl_map_value(map, "hasAuthorityComponent")) {} flutter::EncodableMap CustomSchemeRegistration::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp b/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp index 848aa3917..0ef63dd56 100644 --- a/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp +++ b/flutter_inappwebview_windows/windows/types/custom_scheme_response.cpp @@ -11,9 +11,9 @@ namespace flutter_inappwebview_plugin {} CustomSchemeResponse::CustomSchemeResponse(const flutter::EncodableMap& map) - : data(get_fl_map_value>(map, "data")), - contentType(get_fl_map_value(map, "contentType")), - contentEncoding(get_fl_map_value(map, "contentEncoding")) + : CustomSchemeResponse(get_fl_map_value>(map, "data"), + get_fl_map_value(map, "contentType"), + get_fl_map_value(map, "contentEncoding")) {} flutter::EncodableMap CustomSchemeResponse::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/download_start_request.cpp b/flutter_inappwebview_windows/windows/types/download_start_request.cpp new file mode 100644 index 000000000..b6da8377b --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_request.cpp @@ -0,0 +1,24 @@ +#include "../utils/flutter.h" +#include "download_start_request.h" + +namespace flutter_inappwebview_plugin +{ + DownloadStartRequest::DownloadStartRequest(const std::optional& contentDisposition, + const int64_t& contentLength, + const std::optional& mimeType, + const std::optional& suggestedFilename, + const std::string& url) + : contentDisposition(contentDisposition), contentLength(contentLength), mimeType(mimeType), suggestedFilename(suggestedFilename), url(url) + {} + + flutter::EncodableMap DownloadStartRequest::toEncodableMap() const + { + return flutter::EncodableMap{ + {"contentDisposition", make_fl_value(contentDisposition)}, + {"contentLength", make_fl_value(contentLength)}, + {"mimeType", make_fl_value(mimeType)}, + {"suggestedFilename", make_fl_value(suggestedFilename)}, + {"url", make_fl_value(url)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_request.h b/flutter_inappwebview_windows/windows/types/download_start_request.h new file mode 100644 index 000000000..08844e50c --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_request.h @@ -0,0 +1,30 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class DownloadStartRequest + { + public: + const std::optional contentDisposition; + const int64_t contentLength; + const std::optional mimeType; + const std::optional suggestedFilename; + const std::string url; + + DownloadStartRequest(const std::optional& contentDisposition, + const int64_t& contentLength, + const std::optional& mimeType, + const std::optional& suggestedFilename, + const std::string& url); + ~DownloadStartRequest() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_REQUEST_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_response.cpp b/flutter_inappwebview_windows/windows/types/download_start_response.cpp new file mode 100644 index 000000000..03cce81ec --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_response.cpp @@ -0,0 +1,26 @@ +#include "../utils/flutter.h" +#include "download_start_response.h" + +namespace flutter_inappwebview_plugin +{ + DownloadStartResponse::DownloadStartResponse(const bool& handled, + const std::optional& action, + const std::optional& resultFilePath) + : handled(handled), action(action), resultFilePath(resultFilePath) + {} + + DownloadStartResponse::DownloadStartResponse(const flutter::EncodableMap& map) + : DownloadStartResponse(get_fl_map_value(map, "handled"), + DownloadStartResponseActionFromInteger(get_optional_fl_map_value(map, "action")), + get_optional_fl_map_value(map, "resultFilePath")) + {} + + flutter::EncodableMap DownloadStartResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"handled", make_fl_value(handled)}, + {"action", make_fl_value(DownloadStartResponseActionToInteger(action))}, + {"resultFilePath", make_fl_value(resultFilePath)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/download_start_response.h b/flutter_inappwebview_windows/windows/types/download_start_response.h new file mode 100644 index 000000000..62e4a984a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/download_start_response.h @@ -0,0 +1,60 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class DownloadStartResponseAction { + cancel = 0 + }; + + inline std::optional DownloadStartResponseActionFromInteger(const std::optional& action) + { + if (action.has_value()) { + switch (action.value()) { + case 0: + return DownloadStartResponseAction::cancel; + default: + return DownloadStartResponseAction::cancel; + } + } + return std::optional{}; + } + + inline std::optional DownloadStartResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class DownloadStartResponse + { + public: + const bool handled; + const std::optional action; + const std::optional resultFilePath; + + DownloadStartResponse(const bool& handled, + const std::optional& action, + const std::optional& resultFilePath); + DownloadStartResponse(const flutter::EncodableMap& map); + ~DownloadStartResponse() = default; + + bool DownloadStartResponse::operator==(const DownloadStartResponse& other) + { + return handled == other.handled && + action == other.action && + resultFilePath == other.resultFilePath; + } + bool DownloadStartResponse::operator!=(const DownloadStartResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_DOWNLOAD_START_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/frame_info.cpp b/flutter_inappwebview_windows/windows/types/frame_info.cpp new file mode 100644 index 000000000..7417a3a0d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/frame_info.cpp @@ -0,0 +1,79 @@ +#include "../utils/flutter.h" +#include "../utils/log.h" +#include "../utils/strconv.h" +#include "frame_info.h" + +#include + +namespace flutter_inappwebview_plugin +{ + FrameInfo::FrameInfo(const bool& isMainFrame, + const std::optional> request, + const std::optional> securityOrigin, + const std::optional& name, + const std::optional& frameId, + const std::optional& kind) + : isMainFrame(isMainFrame), request(request), securityOrigin(securityOrigin), name(name), frameId(frameId), kind(kind) + {} + + flutter::EncodableMap FrameInfo::toEncodableMap() const + { + return flutter::EncodableMap{ + {"isMainFrame", make_fl_value(isMainFrame)}, + {"request", request.has_value() ? request.value()->toEncodableMap() : make_fl_value()}, + {"securityOrigin", securityOrigin.has_value() ? securityOrigin.value()->toEncodableMap() : make_fl_value()}, + {"name", make_fl_value(name)}, + {"frameId", make_fl_value(frameId)}, + {"kind", make_fl_value(kind)} + }; + } + + std::unique_ptr FrameInfo::fromICoreWebView2FrameInfo(const wil::com_ptr webViewFrameInfo) + { + wil::unique_cotaskmem_string url; + auto request = std::optional>{}; + auto securityOrigin = std::optional>{}; + if (succeededOrLog(webViewFrameInfo->get_Source(&url))) { + auto sourceUrl = wide_to_utf8(url.get()); + request = std::make_shared( + sourceUrl, + std::optional{}, + std::optional>{}, + std::optional>{} + ); + + if (!sourceUrl.empty()) { + try { + winrt::Windows::Foundation::Uri const uri{ url.get() }; + + securityOrigin = std::make_shared( + wide_to_utf8(uri.Host().c_str()), + uri.Port(), + wide_to_utf8(uri.SchemeName().c_str()) + ); + } + catch (winrt::hresult_error const& ex) { + debugLog(wide_to_utf8(ex.message().c_str())); + } + } + } + + auto webViewFrameInfo2 = webViewFrameInfo.try_query(); + + uint32_t frameId; + wil::unique_cotaskmem_string name; + COREWEBVIEW2_FRAME_KIND kind = COREWEBVIEW2_FRAME_KIND_UNKNOWN; + if (webViewFrameInfo2) { + failedLog(webViewFrameInfo2->get_FrameKind(&kind)); + } + + return std::make_unique( + webViewFrameInfo2 ? kind == COREWEBVIEW2_FRAME_KIND_MAIN_FRAME : false, + request, + securityOrigin, + succeededOrLog(webViewFrameInfo->get_Name(&name)) ? wide_to_utf8(name.get()) : std::optional{}, + webViewFrameInfo2 && succeededOrLog(webViewFrameInfo2->get_FrameId(&frameId)) ? frameId : std::optional{}, + webViewFrameInfo2 ? (int64_t)kind : std::optional{} + ); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/frame_info.h b/flutter_inappwebview_windows/windows/types/frame_info.h new file mode 100644 index 000000000..103c30e1a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/frame_info.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ + +#include +#include +#include +#include +#include + +#include "security_origin.h" +#include "url_request.h" + +namespace flutter_inappwebview_plugin +{ + + class FrameInfo + { + public: + const bool isMainFrame; + const std::optional> request; + const std::optional> securityOrigin; + const std::optional name; + const std::optional frameId; + const std::optional kind; + + FrameInfo(const bool& isMainFrame, + const std::optional> request, + const std::optional> securityOrigin, + const std::optional& name, + const std::optional& frameId, + const std::optional& kind); + ~FrameInfo() = default; + + flutter::EncodableMap toEncodableMap() const; + static std::unique_ptr fromICoreWebView2FrameInfo(const wil::com_ptr webViewFrameInfo); + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_FRAME_INFO_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_auth_response.cpp b/flutter_inappwebview_windows/windows/types/http_auth_response.cpp new file mode 100644 index 000000000..672d02ab7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_auth_response.cpp @@ -0,0 +1,29 @@ +#include "../utils/flutter.h" +#include "http_auth_response.h" + +namespace flutter_inappwebview_plugin +{ + HttpAuthResponse::HttpAuthResponse(const std::string& username, + const std::string& password, + const bool& permanentPersistence, + const std::optional& action) + : username(username), password(password), permanentPersistence(permanentPersistence), action(action) + {} + + HttpAuthResponse::HttpAuthResponse(const flutter::EncodableMap& map) + : HttpAuthResponse(get_fl_map_value(map, "username"), + get_fl_map_value(map, "password"), + get_fl_map_value(map, "permanentPersistence"), + HttpAuthResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap HttpAuthResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)}, + {"permanentPersistence", make_fl_value(permanentPersistence)}, + {"action", make_fl_value(HttpAuthResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_auth_response.h b/flutter_inappwebview_windows/windows/types/http_auth_response.h new file mode 100644 index 000000000..f80ce57a5 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_auth_response.h @@ -0,0 +1,69 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class HttpAuthResponseAction { + cancel = 0, + proceed, + useSavedHttpAuthCredentials // not supported currently + }; + + inline HttpAuthResponseAction HttpAuthResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return HttpAuthResponseAction::cancel; + } + switch (action.value()) { + case 0: + return HttpAuthResponseAction::cancel; + case 1: + return HttpAuthResponseAction::proceed; + case 2: + // not supported currently + // return HttpAuthResponseAction::useSavedHttpAuthCredentials; + default: + return HttpAuthResponseAction::cancel; + } + } + + inline std::optional HttpAuthResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class HttpAuthResponse + { + public: + const std::string username; + const std::string password; + const bool permanentPersistence; + const std::optional action; + + HttpAuthResponse(const std::string& username, + const std::string& password, + const bool& permanentPersistence, + const std::optional& action); + HttpAuthResponse(const flutter::EncodableMap& map); + ~HttpAuthResponse() = default; + + bool HttpAuthResponse::operator==(const HttpAuthResponse& other) + { + return username == other.username && password == other.password && + permanentPersistence == other.permanentPersistence && + action == other.action; + } + bool HttpAuthResponse::operator!=(const HttpAuthResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTH_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp new file mode 100644 index 000000000..6687469dd --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.cpp @@ -0,0 +1,19 @@ +#include "../utils/flutter.h" +#include "http_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + HttpAuthenticationChallenge::HttpAuthenticationChallenge(const std::shared_ptr protectionSpace, + const int64_t& previousFailureCount, + const std::optional> proposedCredential) + : URLAuthenticationChallenge(protectionSpace), previousFailureCount(previousFailureCount), proposedCredential(proposedCredential) + {} + + flutter::EncodableMap HttpAuthenticationChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + map.insert({ "previousFailureCount", make_fl_value(previousFailureCount) }); + map.insert({ "proposedCredential", proposedCredential.has_value() ? proposedCredential.value()->toEncodableMap() : make_fl_value() }); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h new file mode 100644 index 000000000..ff2a26506 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/http_authentication_challenge.h @@ -0,0 +1,27 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ + +#include +#include + +#include "url_authentication_challenge.h" +#include "url_credential.h" + +namespace flutter_inappwebview_plugin +{ + class HttpAuthenticationChallenge : URLAuthenticationChallenge + { + public: + const int64_t previousFailureCount; + const std::optional> proposedCredential; + + HttpAuthenticationChallenge(const std::shared_ptr protectionSpace, + const int64_t& previousFailureCount, + const std::optional> proposedCredential); + ~HttpAuthenticationChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_HTTP_AUTHENTICATION_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp new file mode 100644 index 000000000..af1683e84 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.cpp @@ -0,0 +1,27 @@ +#include "../utils/flutter.h" +#include "../utils/string.h" +#include "javascript_handler_function_data.h" + +namespace flutter_inappwebview_plugin +{ + JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(const std::string& origin, const std::string& requestUrl, const bool& isMainFrame, const std::string& args) + : origin(origin), requestUrl(requestUrl), isMainFrame(isMainFrame), args(args) + {} + + JavaScriptHandlerFunctionData::JavaScriptHandlerFunctionData(const flutter::EncodableMap& map) + : JavaScriptHandlerFunctionData(get_fl_map_value(map, "origin"), + get_fl_map_value(map, "requestUrl"), + get_fl_map_value(map, "isMainFrame"), + get_fl_map_value(map, "args")) + {} + + flutter::EncodableMap JavaScriptHandlerFunctionData::toEncodableMap() const + { + return flutter::EncodableMap{ + {"origin", make_fl_value(origin)}, + {"requestUrl", make_fl_value(requestUrl)}, + {"isMainFrame", make_fl_value(isMainFrame)}, + {"args", make_fl_value(args)} + }; + } +} diff --git a/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h new file mode 100644 index 000000000..aa5a3f6b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/javascript_handler_function_data.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class JavaScriptHandlerFunctionData + { + public: + const std::string origin; + const std::string requestUrl; + const bool isMainFrame; + const std::string args; + + JavaScriptHandlerFunctionData(const std::string& origin, const std::string& requestUrl, const bool& isMainFrame, const std::string& args); + JavaScriptHandlerFunctionData(const flutter::EncodableMap& map); + ~JavaScriptHandlerFunctionData() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_JAVASCRIPT_HANDLER_FUNCTION_DATA_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.cpp b/flutter_inappwebview_windows/windows/types/navigation_action.cpp index 543553e70..d8be38645 100644 --- a/flutter_inappwebview_windows/windows/types/navigation_action.cpp +++ b/flutter_inappwebview_windows/windows/types/navigation_action.cpp @@ -15,7 +15,7 @@ namespace flutter_inappwebview_plugin {"request", request->toEncodableMap()}, {"isForMainFrame", isForMainFrame}, {"isRedirect", make_fl_value(isRedirect)}, - {"navigationType", make_fl_value(navigationType)} + {"navigationType", make_fl_value(NavigationActionTypeToInteger(navigationType))} }; } } \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/navigation_action.h b/flutter_inappwebview_windows/windows/types/navigation_action.h index 886640bef..1dced9307 100644 --- a/flutter_inappwebview_windows/windows/types/navigation_action.h +++ b/flutter_inappwebview_windows/windows/types/navigation_action.h @@ -8,13 +8,18 @@ namespace flutter_inappwebview_plugin { - enum NavigationActionType { + enum class NavigationActionType { linkActivated = 0, backForward, reload, other }; + inline std::optional NavigationActionTypeToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + class NavigationAction { public: diff --git a/flutter_inappwebview_windows/windows/types/permission_response.cpp b/flutter_inappwebview_windows/windows/types/permission_response.cpp index e05598223..b6b7d3dd0 100644 --- a/flutter_inappwebview_windows/windows/types/permission_response.cpp +++ b/flutter_inappwebview_windows/windows/types/permission_response.cpp @@ -7,15 +7,15 @@ namespace flutter_inappwebview_plugin {} PermissionResponse::PermissionResponse(const flutter::EncodableMap& map) - : resources(get_optional_fl_map_value>(map, "resources")), - action(PermissionResponseActionTypeFromInteger(get_optional_fl_map_value(map, "action"))) + : PermissionResponse(get_optional_fl_map_value>(map, "resources"), + PermissionResponseActionTypeFromInteger(get_optional_fl_map_value(map, "action"))) {} flutter::EncodableMap PermissionResponse::toEncodableMap() const { return flutter::EncodableMap{ {"resources", make_fl_value(resources)}, - {"action", make_fl_value(action)}, + {"action", make_fl_value(PermissionResponseActionTypeToInteger(action))}, }; } } \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/permission_response.h b/flutter_inappwebview_windows/windows/types/permission_response.h index e96783146..fd5cf7854 100644 --- a/flutter_inappwebview_windows/windows/types/permission_response.h +++ b/flutter_inappwebview_windows/windows/types/permission_response.h @@ -2,13 +2,12 @@ #define FLUTTER_INAPPWEBVIEW_PLUGIN_PERMISSION_RESPONSE_H_ #include -#include #include "../utils/flutter.h" namespace flutter_inappwebview_plugin { - enum PermissionResponseActionType { + enum class PermissionResponseActionType { deny = 0, grant, prompt @@ -30,6 +29,11 @@ namespace flutter_inappwebview_plugin } } + inline std::optional PermissionResponseActionTypeToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + class PermissionResponse { public: diff --git a/flutter_inappwebview_windows/windows/types/physical_key_status.cpp b/flutter_inappwebview_windows/windows/types/physical_key_status.cpp new file mode 100644 index 000000000..5cbe2d072 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/physical_key_status.cpp @@ -0,0 +1,31 @@ +#include "../utils/flutter.h" +#include "physical_key_status.h" + +namespace flutter_inappwebview_plugin +{ + PhysicalKeyStatus::PhysicalKeyStatus(const int64_t& repeatCount, + const int64_t& scanCode, + const bool& isExtendedKey, + const bool& isMenuKeyDown, + const bool& wasKeyDown, + const bool& isKeyReleased) + : repeatCount(repeatCount), scanCode(scanCode), isExtendedKey(isExtendedKey), isMenuKeyDown(isMenuKeyDown), wasKeyDown(wasKeyDown), isKeyReleased(isKeyReleased) + {} + + std::unique_ptr PhysicalKeyStatus::fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(const COREWEBVIEW2_PHYSICAL_KEY_STATUS& status) + { + return std::make_unique(status.RepeatCount, status.ScanCode, status.IsExtendedKey, status.IsMenuKeyDown, status.WasKeyDown, status.IsKeyReleased); + } + + flutter::EncodableMap PhysicalKeyStatus::toEncodableMap() const + { + return flutter::EncodableMap{ + { "repeatCount", make_fl_value(repeatCount) }, + { "scanCode", make_fl_value(scanCode) }, + { "isExtendedKey", make_fl_value(isExtendedKey) }, + { "isMenuKeyDown", make_fl_value(isMenuKeyDown) }, + { "wasKeyDown", make_fl_value(wasKeyDown) }, + { "isKeyReleased", make_fl_value(isKeyReleased) } + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/physical_key_status.h b/flutter_inappwebview_windows/windows/types/physical_key_status.h new file mode 100644 index 000000000..51fbd33a8 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/physical_key_status.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H + +#include +#include + +namespace flutter_inappwebview_plugin +{ + class PhysicalKeyStatus + { + public: + const int64_t repeatCount; + const int64_t scanCode; + const bool isExtendedKey; + const bool isMenuKeyDown; + const bool wasKeyDown; + const bool isKeyReleased; + + PhysicalKeyStatus(const int64_t& repeatCount, + const int64_t& scanCode, + const bool& isExtendedKey, + const bool& isMenuKeyDown, + const bool& wasKeyDown, + const bool& isKeyReleased); + ~PhysicalKeyStatus() = default; + + static std::unique_ptr fromCOREWEBVIEW2_PHYSICAL_KEY_STATUS(const COREWEBVIEW2_PHYSICAL_KEY_STATUS& status); + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PHYSICAL_KEY_STATUS_H \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.cpp b/flutter_inappwebview_windows/windows/types/plugin_script.cpp index cc37467ae..a292255ae 100644 --- a/flutter_inappwebview_windows/windows/types/plugin_script.cpp +++ b/flutter_inappwebview_windows/windows/types/plugin_script.cpp @@ -6,20 +6,21 @@ namespace flutter_inappwebview_plugin const std::optional& groupName, const std::string& source, const UserScriptInjectionTime& injectionTime, - const std::vector& allowedOriginRules, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, std::shared_ptr contentWorld, const bool& requiredInAllContentWorlds - ) : UserScript(groupName, source, injectionTime, allowedOriginRules, std::move(contentWorld)), + ) : UserScript(groupName, source, injectionTime, forMainFrameOnly, allowedOriginRules, std::move(contentWorld)), requiredInAllContentWorlds_(requiredInAllContentWorlds) {} - std::shared_ptr PluginScript::copyAndSet(const std::shared_ptr cw) const { return std::make_unique( this->groupName, this->source, this->injectionTime, + this->forMainFrameOnly, this->allowedOriginRules, cw, this->requiredInAllContentWorlds_ diff --git a/flutter_inappwebview_windows/windows/types/plugin_script.h b/flutter_inappwebview_windows/windows/types/plugin_script.h index e2fae03fa..c8a6efe52 100644 --- a/flutter_inappwebview_windows/windows/types/plugin_script.h +++ b/flutter_inappwebview_windows/windows/types/plugin_script.h @@ -13,7 +13,8 @@ namespace flutter_inappwebview_plugin const std::optional& groupName, const std::string& source, const UserScriptInjectionTime& injectionTime, - const std::vector& allowedOriginRules, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, std::shared_ptr contentWorld, const bool& requiredInAllContentWorlds ); diff --git a/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp b/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp new file mode 100644 index 000000000..be305a661 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/process_failed_detail.cpp @@ -0,0 +1,26 @@ +#include "../utils/flutter.h" +#include "process_failed_detail.h" + +namespace flutter_inappwebview_plugin +{ + ProcessFailedDetail::ProcessFailedDetail(const int64_t& kind, + const std::optional& exitCode, + const std::optional& processDescription, + const std::optional& reason, + const std::optional& failureSourceModulePath, + const std::optional>>& frameInfos) + : kind(kind), exitCode(exitCode), processDescription(processDescription), reason(reason), failureSourceModulePath(failureSourceModulePath), frameInfos(frameInfos) + {} + + flutter::EncodableMap ProcessFailedDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"kind", make_fl_value(kind)}, + {"exitCode", make_fl_value(exitCode)}, + {"processDescription", make_fl_value(processDescription)}, + {"reason", make_fl_value(reason)}, + {"failureSourceModulePath", make_fl_value(failureSourceModulePath)}, + {"frameInfos", frameInfos.has_value() ? make_fl_value(functional_map(frameInfos.value(), [](const std::shared_ptr& item) { return item->toEncodableMap(); })) : make_fl_value()} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/process_failed_detail.h b/flutter_inappwebview_windows/windows/types/process_failed_detail.h new file mode 100644 index 000000000..555ae4f69 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/process_failed_detail.h @@ -0,0 +1,36 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ + +#include +#include +#include +#include + +#include "frame_info.h" + +namespace flutter_inappwebview_plugin +{ + + class ProcessFailedDetail + { + public: + const int64_t kind; + const std::optional exitCode; + const std::optional processDescription; + const std::optional reason; + const std::optional failureSourceModulePath; + const std::optional>> frameInfos; + + ProcessFailedDetail(const int64_t& kind, + const std::optional& exitCode, + const std::optional& processDescription, + const std::optional& reason, + const std::optional& failureSourceModulePath, + const std::optional>>& frameInfos); + ~ProcessFailedDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_PROCESS_FAILED_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/rect.cpp b/flutter_inappwebview_windows/windows/types/rect.cpp index a0a41ab03..38fd176a1 100644 --- a/flutter_inappwebview_windows/windows/types/rect.cpp +++ b/flutter_inappwebview_windows/windows/types/rect.cpp @@ -7,10 +7,10 @@ namespace flutter_inappwebview_plugin {} Rect::Rect(const flutter::EncodableMap& map) - : x(get_fl_map_value(map, "x")), - y(get_fl_map_value(map, "y")), - width(get_fl_map_value(map, "width")), - height(get_fl_map_value(map, "height")) + : Rect(get_fl_map_value(map, "x"), + get_fl_map_value(map, "y"), + get_fl_map_value(map, "width"), + get_fl_map_value(map, "height")) {} flutter::EncodableMap Rect::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp new file mode 100644 index 000000000..8fbb1238a --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.cpp @@ -0,0 +1,16 @@ +#include "../utils/flutter.h" +#include "render_process_gone_detail.h" + +namespace flutter_inappwebview_plugin +{ + RenderProcessGoneDetail::RenderProcessGoneDetail(const bool& didCrash) + : didCrash(didCrash) + {} + + flutter::EncodableMap RenderProcessGoneDetail::toEncodableMap() const + { + return flutter::EncodableMap{ + {"didCrash", make_fl_value(didCrash)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h new file mode 100644 index 000000000..3e324c1ae --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/render_process_gone_detail.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ + +#include + +namespace flutter_inappwebview_plugin +{ + + class RenderProcessGoneDetail + { + public: + const bool didCrash; + + RenderProcessGoneDetail(const bool& didCrash); + ~RenderProcessGoneDetail() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_RENDER_PROCESS_GONE_DETAIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp index 5e0f027ee..a8ac01fb7 100644 --- a/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.cpp @@ -39,9 +39,9 @@ namespace flutter_inappwebview_plugin {} ScreenshotConfiguration::ScreenshotConfiguration(const flutter::EncodableMap& map) - : compressFormat(CompressFormatFromString(get_fl_map_value(map, "compressFormat"))), - quality(get_fl_map_value(map, "quality")), - rect(fl_map_contains_not_null(map, "rect") ? std::make_shared(get_fl_map_value(map, "rect")) : std::optional>{}) + : ScreenshotConfiguration(CompressFormatFromString(get_fl_map_value(map, "compressFormat")), + get_fl_map_value(map, "quality"), + fl_map_contains_not_null(map, "rect") ? std::make_shared(get_fl_map_value(map, "rect")) : std::optional>{}) {} ScreenshotConfiguration::~ScreenshotConfiguration() {} diff --git a/flutter_inappwebview_windows/windows/types/screenshot_configuration.h b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h index f05ee7f05..b090bac69 100644 --- a/flutter_inappwebview_windows/windows/types/screenshot_configuration.h +++ b/flutter_inappwebview_windows/windows/types/screenshot_configuration.h @@ -10,7 +10,7 @@ namespace flutter_inappwebview_plugin { - enum CompressFormat { + enum class CompressFormat { png, jpeg, webp diff --git a/flutter_inappwebview_windows/windows/types/security_origin.cpp b/flutter_inappwebview_windows/windows/types/security_origin.cpp new file mode 100644 index 000000000..bfd43a329 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/security_origin.cpp @@ -0,0 +1,18 @@ +#include "../utils/flutter.h" +#include "security_origin.h" + +namespace flutter_inappwebview_plugin +{ + SecurityOrigin::SecurityOrigin(const std::string& host, const int64_t& port, const std::string& protocol) + : host(host), port(port), protocol(protocol) + {} + + flutter::EncodableMap SecurityOrigin::toEncodableMap() const + { + return flutter::EncodableMap{ + {"host", make_fl_value(host)}, + {"port", make_fl_value(port)}, + {"protocol", make_fl_value(protocol)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/security_origin.h b/flutter_inappwebview_windows/windows/types/security_origin.h new file mode 100644 index 000000000..84c3fee28 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/security_origin.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + + class SecurityOrigin + { + public: + const std::string host; + const int64_t port; + const std::string protocol; + + SecurityOrigin(const std::string& host, const int64_t& port, const std::string& protocol); + ~SecurityOrigin() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SECURITY_ORIGIN_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp new file mode 100644 index 000000000..e502c0a9e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.cpp @@ -0,0 +1,22 @@ +#include "../utils/flutter.h" +#include "server_trust_auth_response.h" + +namespace flutter_inappwebview_plugin +{ + ServerTrustAuthResponse::ServerTrustAuthResponse( + const std::optional& action) + : action(action) + {} + + ServerTrustAuthResponse::ServerTrustAuthResponse(const flutter::EncodableMap& map) + : ServerTrustAuthResponse( + ServerTrustAuthResponseActionFromInteger(get_optional_fl_map_value(map, "action"))) + {} + + flutter::EncodableMap ServerTrustAuthResponse::toEncodableMap() const + { + return flutter::EncodableMap{ + {"action", make_fl_value(ServerTrustAuthResponseActionToInteger(action))} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h new file mode 100644 index 000000000..af2c7ba2e --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_auth_response.h @@ -0,0 +1,55 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + enum class ServerTrustAuthResponseAction { + cancel = 0, + proceed + }; + + inline ServerTrustAuthResponseAction ServerTrustAuthResponseActionFromInteger(const std::optional& action) + { + if (!action.has_value()) { + return ServerTrustAuthResponseAction::cancel; + } + switch (action.value()) { + case 1: + return ServerTrustAuthResponseAction::proceed; + case 0: + default: + return ServerTrustAuthResponseAction::cancel; + } + } + + inline std::optional ServerTrustAuthResponseActionToInteger(const std::optional& action) + { + return action.has_value() ? static_cast(action.value()) : std::optional{}; + } + + class ServerTrustAuthResponse + { + public: + const std::optional action; + + ServerTrustAuthResponse(const std::optional& action); + ServerTrustAuthResponse(const flutter::EncodableMap& map); + ~ServerTrustAuthResponse() = default; + + bool ServerTrustAuthResponse::operator==(const ServerTrustAuthResponse& other) + { + return action == other.action; + } + bool ServerTrustAuthResponse::operator!=(const ServerTrustAuthResponse& other) + { + return !(*this == other); + } + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_AUTH_RESPONSE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp b/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp new file mode 100644 index 000000000..d20c8c285 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_challenge.cpp @@ -0,0 +1,15 @@ +#include "../utils/flutter.h" +#include "server_trust_challenge.h" + +namespace flutter_inappwebview_plugin +{ + ServerTrustChallenge::ServerTrustChallenge(const std::shared_ptr protectionSpace) + : URLAuthenticationChallenge(protectionSpace) + {} + + flutter::EncodableMap ServerTrustChallenge::toEncodableMap() const + { + auto map = URLAuthenticationChallenge::toEncodableMap(); + return map; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/server_trust_challenge.h b/flutter_inappwebview_windows/windows/types/server_trust_challenge.h new file mode 100644 index 000000000..9121ec800 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/server_trust_challenge.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ + +#include +#include + +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + class ServerTrustChallenge : URLAuthenticationChallenge + { + public: + ServerTrustChallenge(const std::shared_ptr protectionSpace); + ~ServerTrustChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SERVER_TRUST_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/size_2d.cpp b/flutter_inappwebview_windows/windows/types/size_2d.cpp index e12390266..7a790854d 100644 --- a/flutter_inappwebview_windows/windows/types/size_2d.cpp +++ b/flutter_inappwebview_windows/windows/types/size_2d.cpp @@ -2,13 +2,13 @@ namespace flutter_inappwebview_plugin { - Size2D::Size2D(const double& width, const double& height) + Size2D::Size2D(const double& width, const double& height) : width(width), height(height) {} - Size2D::Size2D(const flutter::EncodableMap& map) - : width(get_fl_map_value(map, "width")), - height(get_fl_map_value(map, "height")) + Size2D::Size2D(const flutter::EncodableMap& map) + : Size2D(get_fl_map_value(map, "width"), + get_fl_map_value(map, "height")) {} flutter::EncodableMap Size2D::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/ssl_error.cpp b/flutter_inappwebview_windows/windows/types/ssl_error.cpp new file mode 100644 index 000000000..1147862d0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_error.cpp @@ -0,0 +1,17 @@ +#include "../utils/flutter.h" +#include "ssl_error.h" + +namespace flutter_inappwebview_plugin +{ + SslError::SslError(const COREWEBVIEW2_WEB_ERROR_STATUS& code, const std::optional& message) + : code(code), message(message) + {} + + flutter::EncodableMap SslError::toEncodableMap() const + { + return flutter::EncodableMap{ + {"code", make_fl_value((int64_t)code)}, + {"message", make_fl_value(message)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/ssl_error.h b/flutter_inappwebview_windows/windows/types/ssl_error.h new file mode 100644 index 000000000..a86204577 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/ssl_error.h @@ -0,0 +1,44 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ + +#include +#include +#include + +#include "WebView2.h" + +namespace flutter_inappwebview_plugin +{ + inline std::optional COREWEBVIEW2_WEB_ERROR_STATUS_ToString(const COREWEBVIEW2_WEB_ERROR_STATUS& code) + { + switch (code) { + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT: + return "Indicates that the SSL certificate common name does not match the web address."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_EXPIRED: + return "Indicates that the SSL certificate has expired."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CLIENT_CERTIFICATE_CONTAINS_ERRORS: + return "Indicates that the SSL client certificate contains errors."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_REVOKED: + return "Indicates that the SSL certificate has been revoked."; + case COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID: + return "Indicates that the SSL certificate is not valid."; + default: + break; + } + return std::optional{}; + } + + class SslError + { + public: + const COREWEBVIEW2_WEB_ERROR_STATUS code; + const std::optional message; + + SslError(const COREWEBVIEW2_WEB_ERROR_STATUS& code, const std::optional& message); + ~SslError() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_SSL_ERROR_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp new file mode 100644 index 000000000..7b1a5ff72 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.cpp @@ -0,0 +1,16 @@ +#include "../utils/flutter.h" +#include "url_authentication_challenge.h" + +namespace flutter_inappwebview_plugin +{ + URLAuthenticationChallenge::URLAuthenticationChallenge(const std::shared_ptr protectionSpace) + : protectionSpace(protectionSpace) + {} + + flutter::EncodableMap URLAuthenticationChallenge::toEncodableMap() const + { + return flutter::EncodableMap{ + {"protectionSpace", protectionSpace->toEncodableMap()}, + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h new file mode 100644 index 000000000..80688107f --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_authentication_challenge.h @@ -0,0 +1,22 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ + +#include + +#include "url_protection_space.h" + +namespace flutter_inappwebview_plugin +{ + class URLAuthenticationChallenge + { + public: + const std::shared_ptr protectionSpace; + + URLAuthenticationChallenge(const std::shared_ptr protectionSpace); + ~URLAuthenticationChallenge() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_AUTHENTICATION_CHALLENGE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_credential.cpp b/flutter_inappwebview_windows/windows/types/url_credential.cpp new file mode 100644 index 000000000..f02c7565d --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_credential.cpp @@ -0,0 +1,18 @@ +#include "../utils/flutter.h" +#include "url_credential.h" + +namespace flutter_inappwebview_plugin +{ + URLCredential::URLCredential(const std::optional& username, + const std::optional& password) + : username(username), password(password) + {} + + flutter::EncodableMap URLCredential::toEncodableMap() const + { + return flutter::EncodableMap{ + {"username", make_fl_value(username)}, + {"password", make_fl_value(password)} + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_credential.h b/flutter_inappwebview_windows/windows/types/url_credential.h new file mode 100644 index 000000000..2f40bb7d4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_credential.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ + +#include +#include +#include + +namespace flutter_inappwebview_plugin +{ + class URLCredential + { + public: + const std::optional username; + const std::optional password; + + URLCredential(const std::optional& username, + const std::optional& password); + ~URLCredential() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_CREDENTIAL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_protection_space.cpp b/flutter_inappwebview_windows/windows/types/url_protection_space.cpp new file mode 100644 index 000000000..26d62af97 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_protection_space.cpp @@ -0,0 +1,24 @@ +#include "../utils/flutter.h" +#include "url_protection_space.h" + +namespace flutter_inappwebview_plugin +{ + URLProtectionSpace::URLProtectionSpace(const std::string& host, const std::string& protocol, + const std::optional& realm, const int64_t& port, + const std::optional> sslCertificate, + const std::optional> sslError) + : host(host), protocol(protocol), realm(realm), port(port), sslCertificate(sslCertificate), sslError(sslError) + {} + + flutter::EncodableMap URLProtectionSpace::toEncodableMap() const + { + return flutter::EncodableMap{ + {"host", make_fl_value(host)}, + {"protocol", make_fl_value(protocol)}, + {"realm", make_fl_value(realm)}, + {"port", make_fl_value(port)}, + {"sslCertificate", sslCertificate.has_value() ? sslCertificate.value()->toEncodableMap() : make_fl_value()}, + {"sslError", sslError.has_value() ? sslError.value()->toEncodableMap() : make_fl_value()}, + }; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_protection_space.h b/flutter_inappwebview_windows/windows/types/url_protection_space.h new file mode 100644 index 000000000..44265c0b6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/types/url_protection_space.h @@ -0,0 +1,33 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ + +#include +#include +#include + +#include "ssl_certificate.h" +#include "ssl_error.h" + +namespace flutter_inappwebview_plugin +{ + class URLProtectionSpace + { + public: + const std::string host; + const std::string protocol; + const std::optional realm; + const int64_t port; + const std::optional> sslCertificate; + const std::optional> sslError; + + URLProtectionSpace(const std::string& host, const std::string& protocol, + const std::optional& realm, const int64_t& port, + const std::optional> sslCertificate, + const std::optional> sslError); + ~URLProtectionSpace() = default; + + flutter::EncodableMap toEncodableMap() const; + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URL_PROTECTION_SPACE_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/types/url_request.cpp b/flutter_inappwebview_windows/windows/types/url_request.cpp index 42340dd08..7335b0bd7 100644 --- a/flutter_inappwebview_windows/windows/types/url_request.cpp +++ b/flutter_inappwebview_windows/windows/types/url_request.cpp @@ -9,10 +9,10 @@ namespace flutter_inappwebview_plugin {} URLRequest::URLRequest(const flutter::EncodableMap& map) - : url(get_optional_fl_map_value(map, "url")), - method(get_optional_fl_map_value(map, "method")), - headers(get_optional_fl_map_value>(map, "headers")), - body(get_optional_fl_map_value>(map, "body")) + : URLRequest(get_optional_fl_map_value(map, "url"), + get_optional_fl_map_value(map, "method"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value>(map, "body")) {} flutter::EncodableMap URLRequest::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/user_script.cpp b/flutter_inappwebview_windows/windows/types/user_script.cpp index 6a783fe08..c74d705b3 100644 --- a/flutter_inappwebview_windows/windows/types/user_script.cpp +++ b/flutter_inappwebview_windows/windows/types/user_script.cpp @@ -1,25 +1,32 @@ -#include "../utils/map.h" #include "user_script.h" +#include "../utils/log.h" +#include "../utils/string.h" + namespace flutter_inappwebview_plugin { UserScript::UserScript( const std::optional& groupName, const std::string& source, const UserScriptInjectionTime& injectionTime, - const std::vector& allowedOriginRules, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, std::shared_ptr contentWorld - ) : groupName(groupName), source(source), injectionTime(injectionTime), allowedOriginRules(allowedOriginRules), contentWorld(std::move(contentWorld)) + ) : groupName(groupName), source(source), + injectionTime(injectionTime), forMainFrameOnly(forMainFrameOnly), allowedOriginRules(allowedOriginRules), contentWorld(std::move(contentWorld)) {} UserScript::UserScript(const flutter::EncodableMap& map) - : groupName(get_optional_fl_map_value(map, "groupName")), - source(get_fl_map_value(map, "source")), - injectionTime(static_cast(get_fl_map_value(map, "injectionTime"))), - allowedOriginRules(functional_map(get_fl_map_value(map, "allowedOriginRules"), [](const flutter::EncodableValue& m) { return std::get(m); })), - contentWorld(std::make_shared(get_fl_map_value(map, "contentWorld"))) + : UserScript(get_optional_fl_map_value(map, "groupName"), + get_fl_map_value(map, "source"), + static_cast(get_fl_map_value(map, "injectionTime")), + get_fl_map_value(map, "forMainFrameOnly"), + fl_map_contains_not_null(map, "allowedOriginRules") ? + functional_map(get_fl_map_value(map, "allowedOriginRules"), [](const flutter::EncodableValue& m) { return std::get(m); }) + : std::optional>{}, + std::make_shared(get_fl_map_value(map, "contentWorld"))) {} UserScript::~UserScript() {} -} \ No newline at end of file +} diff --git a/flutter_inappwebview_windows/windows/types/user_script.h b/flutter_inappwebview_windows/windows/types/user_script.h index 473661f0e..1ea9d472d 100644 --- a/flutter_inappwebview_windows/windows/types/user_script.h +++ b/flutter_inappwebview_windows/windows/types/user_script.h @@ -11,7 +11,7 @@ namespace flutter_inappwebview_plugin { - enum UserScriptInjectionTime { + enum class UserScriptInjectionTime { atDocumentStart = 0, atDocumentEnd }; @@ -23,14 +23,16 @@ namespace flutter_inappwebview_plugin const std::optional groupName; const std::string source; const UserScriptInjectionTime injectionTime; - const std::vector allowedOriginRules; + const bool forMainFrameOnly; + const std::optional> allowedOriginRules; const std::shared_ptr contentWorld; UserScript( const std::optional& groupName, const std::string& source, const UserScriptInjectionTime& injectionTime, - const std::vector& allowedOriginRules, + const bool& forMainFrameOnly, + const std::optional>& allowedOriginRules, std::shared_ptr contentWorld ); UserScript(const flutter::EncodableMap& map); diff --git a/flutter_inappwebview_windows/windows/types/web_history.cpp b/flutter_inappwebview_windows/windows/types/web_history.cpp index aa89d9a2d..1790b61f7 100644 --- a/flutter_inappwebview_windows/windows/types/web_history.cpp +++ b/flutter_inappwebview_windows/windows/types/web_history.cpp @@ -8,8 +8,8 @@ namespace flutter_inappwebview_plugin {} WebHistory::WebHistory(const flutter::EncodableMap& map) - : currentIndex(get_optional_fl_map_value(map, "currentIndex")), - list(functional_map(get_optional_fl_map_value(map, "list"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })) + : WebHistory(get_optional_fl_map_value(map, "currentIndex"), + functional_map(get_optional_fl_map_value(map, "list"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })) {} flutter::EncodableMap WebHistory::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/web_history_item.cpp b/flutter_inappwebview_windows/windows/types/web_history_item.cpp index 11350eebb..d2849df82 100644 --- a/flutter_inappwebview_windows/windows/types/web_history_item.cpp +++ b/flutter_inappwebview_windows/windows/types/web_history_item.cpp @@ -9,12 +9,12 @@ namespace flutter_inappwebview_plugin {} WebHistoryItem::WebHistoryItem(const flutter::EncodableMap& map) - : entryId(get_optional_fl_map_value(map, "entryId")), - index(get_optional_fl_map_value(map, "index")), - offset(get_optional_fl_map_value(map, "offset")), - originalUrl(get_optional_fl_map_value(map, "originalUrl")), - title(get_optional_fl_map_value(map, "title")), - url(get_optional_fl_map_value(map, "url")) + : WebHistoryItem(get_optional_fl_map_value(map, "entryId"), + get_optional_fl_map_value(map, "index"), + get_optional_fl_map_value(map, "offset"), + get_optional_fl_map_value(map, "originalUrl"), + get_optional_fl_map_value(map, "title"), + get_optional_fl_map_value(map, "url")) {} flutter::EncodableMap WebHistoryItem::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/web_resource_error.cpp b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp index 5059a3dcf..5f674d597 100644 --- a/flutter_inappwebview_windows/windows/types/web_resource_error.cpp +++ b/flutter_inappwebview_windows/windows/types/web_resource_error.cpp @@ -8,8 +8,8 @@ namespace flutter_inappwebview_plugin {} WebResourceError::WebResourceError(const flutter::EncodableMap& map) - : description(get_fl_map_value(map, "description")), - type(get_fl_map_value(map, "type")) + : WebResourceError(get_fl_map_value(map, "description"), + get_fl_map_value(map, "type")) {} flutter::EncodableMap WebResourceError::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/types/web_resource_request.cpp b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp index 437fa1903..42896c204 100644 --- a/flutter_inappwebview_windows/windows/types/web_resource_request.cpp +++ b/flutter_inappwebview_windows/windows/types/web_resource_request.cpp @@ -10,10 +10,10 @@ namespace flutter_inappwebview_plugin {} WebResourceRequest::WebResourceRequest(const flutter::EncodableMap& map) - : url(get_optional_fl_map_value(map, "url")), - method(get_optional_fl_map_value(map, "method")), - headers(get_optional_fl_map_value>(map, "headers")), - isForMainFrame(get_optional_fl_map_value(map, "isForMainFrame")) + : WebResourceRequest(get_optional_fl_map_value(map, "url"), + get_optional_fl_map_value(map, "method"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value(map, "isForMainFrame")) {} WebResourceRequest::WebResourceRequest(wil::com_ptr webResourceRequest) diff --git a/flutter_inappwebview_windows/windows/types/web_resource_response.cpp b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp index bd17832c6..430cf1afe 100644 --- a/flutter_inappwebview_windows/windows/types/web_resource_response.cpp +++ b/flutter_inappwebview_windows/windows/types/web_resource_response.cpp @@ -17,12 +17,12 @@ namespace flutter_inappwebview_plugin {} WebResourceResponse::WebResourceResponse(const flutter::EncodableMap& map) - : contentType(get_optional_fl_map_value(map, "contentType")), - contentEncoding(get_optional_fl_map_value(map, "contentEncoding")), - statusCode(get_optional_fl_map_value(map, "statusCode")), - reasonPhrase(get_optional_fl_map_value(map, "reasonPhrase")), - headers(get_optional_fl_map_value>(map, "headers")), - data(get_optional_fl_map_value>(map, "data")) + : WebResourceResponse(get_optional_fl_map_value(map, "contentType"), + get_optional_fl_map_value(map, "contentEncoding"), + get_optional_fl_map_value(map, "statusCode"), + get_optional_fl_map_value(map, "reasonPhrase"), + get_optional_fl_map_value>(map, "headers"), + get_optional_fl_map_value>(map, "data")) {} flutter::EncodableMap WebResourceResponse::toEncodableMap() const diff --git a/flutter_inappwebview_windows/windows/utils/base64.cpp b/flutter_inappwebview_windows/windows/utils/base64.cpp index 843719ec9..938caae36 100644 --- a/flutter_inappwebview_windows/windows/utils/base64.cpp +++ b/flutter_inappwebview_windows/windows/utils/base64.cpp @@ -36,11 +36,11 @@ #include #include - // - // Depending on the url parameter in base64_chars, one of - // two sets of base64 characters needs to be chosen. - // They differ in their last two characters. - // +// +// Depending on the url parameter in base64_chars, one of +// two sets of base64 characters needs to be chosen. +// They differ in their last two characters. +// static const char* base64_chars[2] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" @@ -50,147 +50,155 @@ static const char* base64_chars[2] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" - "-_"}; - -static unsigned int pos_of_char(const unsigned char chr) { - // - // Return the position of chr within base64_encode() - // - - if (chr >= 'A' && chr <= 'Z') return chr - 'A'; - else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; - else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; - else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( - else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' - else - // - // 2020-10-23: Throw std::exception rather than const char* - //(Pablo Martin-Gomez, https://github.com/Bouska) - // + "-_" }; + +static unsigned int pos_of_char(const unsigned char chr) +{ + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // throw std::runtime_error("Input is not valid base64-encoded data."); } -static std::string insert_linebreaks(std::string str, size_t distance) { - // - // Provided by https://github.com/JomaCorpFX, adapted by me. - // - if (!str.length()) { - return ""; - } +static std::string insert_linebreaks(std::string str, size_t distance) +{ + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return ""; + } - size_t pos = distance; + size_t pos = distance; - while (pos < str.size()) { - str.insert(pos, "\n"); - pos += distance + 1; - } + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } - return str; + return str; } template -static std::string encode_with_line_breaks(String s) { +static std::string encode_with_line_breaks(String s) +{ return insert_linebreaks(base64_encode(s, false), line_length); } template -static std::string encode_pem(String s) { +static std::string encode_pem(String s) +{ return encode_with_line_breaks(s); } template -static std::string encode_mime(String s) { +static std::string encode_mime(String s) +{ return encode_with_line_breaks(s); } template -static std::string encode(String s, bool url) { +static std::string encode(String s, bool url) +{ return base64_encode(reinterpret_cast(s.data()), s.length(), url); } -std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) +{ - size_t len_encoded = (in_len +2) / 3 * 4; + size_t len_encoded = (in_len + 2) / 3 * 4; - unsigned char trailing_char = url ? '.' : '='; + unsigned char trailing_char = url ? '.' : '='; - // - // Choose set of base64 characters. They differ - // for the last two positions, depending on the url - // parameter. - // A bool (as is the parameter url) is guaranteed - // to evaluate to either 0 or 1 in C++ therefore, - // the correct character set is chosen by subscripting - // base64_chars with url. - // - const char* base64_chars_ = base64_chars[url]; + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; - std::string ret; - ret.reserve(len_encoded); + std::string ret; + ret.reserve(len_encoded); - unsigned int pos = 0; + unsigned int pos = 0; - while (pos < in_len) { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); - if (pos+1 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + if (pos + 1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); - if (pos+2 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); - ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); - } - else { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); - ret.push_back(trailing_char); - } - } - else { - - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); - ret.push_back(trailing_char); - ret.push_back(trailing_char); - } + if (pos + 2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { - pos += 3; + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); } + pos += 3; + } + - return ret; + return ret; } template -static std::string decode(String const& encoded_string, bool remove_linebreaks) { - // - // decode(…) is templated so that it can be used with String = const std::string& - // or std::string_view (requires at least C++17) - // +static std::string decode(String const& encoded_string, bool remove_linebreaks) +{ + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // - if (encoded_string.empty()) return std::string(); + if (encoded_string.empty()) return std::string(); - if (remove_linebreaks) { + if (remove_linebreaks) { - std::string copy(encoded_string); + std::string copy(encoded_string); - copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); - return base64_decode(copy, false); - } + return base64_decode(copy, false); + } - size_t length_of_string = encoded_string.length(); - size_t pos = 0; + size_t length_of_string = encoded_string.length(); + size_t pos = 0; - // - // The approximate length (bytes) of the decoded string might be one or - // two bytes smaller, depending on the amount of trailing equal signs - // in the encoded string. This approximation is needed to reserve - // enough space in the string to be returned. - // - size_t approx_length_of_decoded_string = length_of_string / 4 * 3; - std::string ret; - ret.reserve(approx_length_of_decoded_string); + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); - while (pos < length_of_string) { + while (pos < length_of_string) { // // Iterate over encoded input string in chunks. The size of all // chunks except the last one is 4 bytes. @@ -204,56 +212,58 @@ static std::string decode(String const& encoded_string, bool remove_linebreaks) // The last chunk produces at least one and up to three bytes. // - size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos + 1)); // // Emit the first output byte that is produced in each chunk: // - ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); - - if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) - encoded_string.at(pos+2) != '=' && - encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. - ) - { - // - // Emit a chunk's second byte (which might not be produced in the last chunk). - // - unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); - ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); - - if ( ( pos + 3 < length_of_string ) && - encoded_string.at(pos+3) != '=' && - encoded_string.at(pos+3) != '.' - ) - { - // - // Emit a chunk's third byte (which might not be produced in the last chunk). - // - ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); - } - } - - pos += 4; + ret.push_back(static_cast(((pos_of_char(encoded_string.at(pos + 0))) << 2) + ((pos_of_char_1 & 0x30) >> 4))); + + if ((pos + 2 < length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos + 2) != '=' && + encoded_string.at(pos + 2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos + 2)); + ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); + + if ((pos + 3 < length_of_string) && + encoded_string.at(pos + 3) != '=' && + encoded_string.at(pos + 3) != '.' + ) { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string.at(pos + 3)))); + } } - return ret; + pos += 4; + } + + return ret; } -std::string base64_decode(std::string const& s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); +std::string base64_decode(std::string const& s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); } -std::string base64_encode(std::string const& s, bool url) { - return encode(s, url); +std::string base64_encode(std::string const& s, bool url) +{ + return encode(s, url); } -std::string base64_encode_pem (std::string const& s) { - return encode_pem(s); +std::string base64_encode_pem(std::string const& s) +{ + return encode_pem(s); } -std::string base64_encode_mime(std::string const& s) { - return encode_mime(s); +std::string base64_encode_mime(std::string const& s) +{ + return encode_mime(s); } #if __cplusplus >= 201703L @@ -263,20 +273,24 @@ std::string base64_encode_mime(std::string const& s) { // Provided by Yannic Bonenberger (https://github.com/Yannic) // -std::string base64_encode(std::string_view s, bool url) { - return encode(s, url); +std::string base64_encode(std::string_view s, bool url) +{ + return encode(s, url); } -std::string base64_encode_pem(std::string_view s) { - return encode_pem(s); +std::string base64_encode_pem(std::string_view s) +{ + return encode_pem(s); } -std::string base64_encode_mime(std::string_view s) { - return encode_mime(s); +std::string base64_encode_mime(std::string_view s) +{ + return encode_mime(s); } -std::string base64_decode(std::string_view s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); +std::string base64_decode(std::string_view s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); } #endif // __cplusplus >= 201703L diff --git a/flutter_inappwebview_windows/windows/utils/base64.h b/flutter_inappwebview_windows/windows/utils/base64.h index 4860d63d8..5e74e2f70 100644 --- a/flutter_inappwebview_windows/windows/utils/base64.h +++ b/flutter_inappwebview_windows/windows/utils/base64.h @@ -12,8 +12,8 @@ #include #endif // __cplusplus >= 201703L -std::string base64_encode (std::string const& s, bool url = false); -std::string base64_encode_pem (std::string const& s); +std::string base64_encode(std::string const& s, bool url = false); +std::string base64_encode_pem(std::string const& s); std::string base64_encode_mime(std::string const& s); std::string base64_decode(std::string const& s, bool remove_linebreaks = false); @@ -25,8 +25,8 @@ std::string base64_encode(unsigned char const*, size_t len, bool url = false); // Requires C++17 // Provided by Yannic Bonenberger (https://github.com/Yannic) // -std::string base64_encode (std::string_view s, bool url = false); -std::string base64_encode_pem (std::string_view s); +std::string base64_encode(std::string_view s, bool url = false); +std::string base64_encode_pem(std::string_view s); std::string base64_encode_mime(std::string_view s); std::string base64_decode(std::string_view s, bool remove_linebreaks = false); diff --git a/flutter_inappwebview_windows/windows/utils/defer.h b/flutter_inappwebview_windows/windows/utils/defer.h new file mode 100644 index 000000000..80a6b6ba2 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/defer.h @@ -0,0 +1,15 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ + +#include +#include + +namespace flutter_inappwebview_plugin +{ + static inline std::shared_ptr defer(void* handle, const std::function& callback) + { + return std::shared_ptr(handle, callback); + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_DEFER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/flutter.h b/flutter_inappwebview_windows/windows/utils/flutter.h index 45c1390cf..b3f03339e 100644 --- a/flutter_inappwebview_windows/windows/utils/flutter.h +++ b/flutter_inappwebview_windows/windows/utils/flutter.h @@ -122,8 +122,8 @@ namespace flutter_inappwebview_plugin template::value, bool>::type* = nullptr> static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) { - if (fl_map_contains_not_null(map, key)) { - auto fl_key = make_fl_value(key); + auto fl_key = make_fl_value(key); + if (fl_map_contains_not_null(map, key) && (std::holds_alternative(map.at(fl_key)) || std::holds_alternative(map.at(fl_key)))) { return std::make_optional(map.at(fl_key).LongValue()); } return std::nullopt; @@ -132,8 +132,8 @@ namespace flutter_inappwebview_plugin template::value, bool>::type* = nullptr> static inline std::optional get_optional_fl_map_value(const flutter::EncodableMap& map, const char* key) { - if (fl_map_contains_not_null(map, key)) { - auto fl_key = make_fl_value(key); + auto fl_key = make_fl_value(key); + if (fl_map_contains_not_null(map, key) && (std::holds_alternative(map.at(fl_key)) || std::holds_alternative(map.at(fl_key)))) { return std::make_optional(map.at(fl_key).LongValue()); } return std::nullopt; @@ -177,7 +177,7 @@ namespace flutter_inappwebview_plugin auto flList = std::get_if(&map.at(make_fl_value(key))); if (flList) { - T vecValue(flList->size()); + T vecValue; for (auto itr = flList->begin(); itr != flList->end(); itr++) { vecValue.push_back(std::get(*itr)); } diff --git a/flutter_inappwebview_windows/windows/utils/string.h b/flutter_inappwebview_windows/windows/utils/string.h index 618e18f1c..50e18db24 100644 --- a/flutter_inappwebview_windows/windows/utils/string.h +++ b/flutter_inappwebview_windows/windows/utils/string.h @@ -188,6 +188,14 @@ namespace flutter_inappwebview_plugin { return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } + + constexpr uint32_t string_hash(const std::string_view data) noexcept + { + uint32_t hash = 5381; + for (const auto& e : data) + hash = ((hash << 5) + hash) + e; + return hash; + }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_STRING_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/timer.h b/flutter_inappwebview_windows/windows/utils/timer.h new file mode 100644 index 000000000..198293384 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/timer.h @@ -0,0 +1,70 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ + +#include +#include +#include + +#include "map.h" + +namespace flutter_inappwebview_plugin +{ + class Timer { + public: + static UINT_PTR setTimeout(std::function callback, uint32_t delay) + { + auto timerId = SetTimer(NULL, 0, delay, (TIMERPROC)&Timer::timerCallback_); + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer#return-value + if (timerId != 0) { + timeoutCallbacks_[timerId] = callback; + } + return timerId; + } + + static UINT_PTR setInterval(std::function callback, uint32_t delay) + { + auto timerId = SetTimer(NULL, 0, delay, (TIMERPROC)&Timer::timerCallback_); + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer#return-value + if (timerId != 0) { + intervalCallbacks_[timerId] = callback; + } + return timerId; + } + + static bool clearTimeout(UINT_PTR timerId) + { + if (map_contains(timeoutCallbacks_, timerId)) { + timeoutCallbacks_.erase(timerId); + return (bool)KillTimer(NULL, timerId); + } + return false; + } + + static bool clearInterval(UINT_PTR timerId) + { + if (map_contains(intervalCallbacks_, timerId)) { + intervalCallbacks_.erase(timerId); + return (bool)KillTimer(NULL, timerId); + } + return false; + } + private: + static inline std::map> timeoutCallbacks_ = {}; + static inline std::map> intervalCallbacks_ = {}; + static void CALLBACK timerCallback_(HWND hwnd, UINT uMsg, UINT_PTR timerId, DWORD dwTime) + { + if (map_contains(timeoutCallbacks_, timerId)) { + timeoutCallbacks_.at(timerId)(); + clearTimeout(timerId); + } + else if (map_contains(intervalCallbacks_, timerId)) { + intervalCallbacks_.at(timerId)(); + } + else { + KillTimer(NULL, timerId); + } + } + }; +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_TIMER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/uri.h b/flutter_inappwebview_windows/windows/utils/uri.h new file mode 100644 index 000000000..6705a358a --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/uri.h @@ -0,0 +1,39 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ + +#include +#include + +#include "string.h" + +namespace flutter_inappwebview_plugin { + static inline std::string get_origin_from_url(const std::string &url) { + try { + winrt::Windows::Foundation::Uri const uri{utf8_to_wide(url)}; + auto scheme = winrt::to_string(uri.SchemeName()); + auto host = winrt::to_string(uri.Host()); + if (!scheme.empty() && !host.empty()) { + auto uriPort = uri.Port(); + std::string port = ""; + if (uriPort > 0 && ((string_equals(scheme, "http") && uriPort != 80) || + (string_equals(scheme, "https") && uriPort != 443))) { + port = ":" + std::to_string(uriPort); + } + return scheme + "://" + host + port; + } + } + catch (...) {} + auto urlSplit = split(url, std::string{"://"}); + if (urlSplit.size() > 1) { + auto scheme = urlSplit[0]; + auto afterScheme = urlSplit[1]; + auto afterSchemeSplit = split(afterScheme, std::string{"/"}); + auto host = afterSchemeSplit[0]; + return scheme + "://" + host; + } + + return url; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_URI_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/utils/util.h b/flutter_inappwebview_windows/windows/utils/util.h index bbac292d2..99e6d9960 100644 --- a/flutter_inappwebview_windows/windows/utils/util.h +++ b/flutter_inappwebview_windows/windows/utils/util.h @@ -2,9 +2,9 @@ #define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ #include +#include #include #include -#include namespace flutter_inappwebview_plugin { diff --git a/flutter_inappwebview_windows/windows/utils/uuid.h b/flutter_inappwebview_windows/windows/utils/uuid.h new file mode 100644 index 000000000..cf7396621 --- /dev/null +++ b/flutter_inappwebview_windows/windows/utils/uuid.h @@ -0,0 +1,25 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ + +#include +#include + +namespace flutter_inappwebview_plugin { + static inline std::string get_uuid() + { + UUID uuid = { 0 }; + std::string guid; + + ::UuidCreate(&uuid); + + RPC_CSTR szUuid = NULL; + if (::UuidToStringA(&uuid, &szUuid) == RPC_S_OK) { + guid = (char*)szUuid; + ::RpcStringFreeA(&szUuid); + } + + return guid; + } +} + +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_UUID_UTIL_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp index 580a1787f..e8c5669b8 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.cpp @@ -46,6 +46,14 @@ namespace flutter_inappwebview_plugin if (settings->targetCompatibleBrowserVersion.has_value()) { options->put_TargetCompatibleBrowserVersion(utf8_to_wide(settings->targetCompatibleBrowserVersion.value()).c_str()); } + wil::com_ptr options2; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options2))) && settings->exclusiveUserDataFolderAccess.has_value()) { + options2->put_ExclusiveUserDataFolderAccess(settings->exclusiveUserDataFolderAccess.value()); + } + wil::com_ptr options3; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options3))) && settings->isCustomCrashReportingEnabled.has_value()) { + options3->put_IsCustomCrashReportingEnabled(settings->isCustomCrashReportingEnabled.value()); + } wil::com_ptr options4; if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options4))) && settings->customSchemeRegistrations.has_value()) { std::vector registrations = {}; @@ -54,6 +62,27 @@ namespace flutter_inappwebview_plugin } options4->SetCustomSchemeRegistrations(static_cast(registrations.size()), registrations.data()); } + wil::com_ptr options5; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options5))) && settings->enableTrackingPrevention.has_value()) { + options5->put_EnableTrackingPrevention(settings->enableTrackingPrevention.value()); + } + wil::com_ptr options6; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options6))) && settings->areBrowserExtensionsEnabled.has_value()) { + options6->put_AreBrowserExtensionsEnabled(settings->areBrowserExtensionsEnabled.value()); + } + wil::com_ptr options7; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options7)))) { + if (settings->channelSearchKind.has_value()) { + options7->put_ChannelSearchKind(static_cast(settings->channelSearchKind.value())); + } + if (settings->releaseChannels.has_value()) { + options7->put_ReleaseChannels(static_cast(settings->releaseChannels.value())); + } + } + wil::com_ptr options8; + if (succeededOrLog(options->QueryInterface(IID_PPV_ARGS(&options8))) && settings->scrollbarStyle.has_value()) { + options8->put_ScrollBarStyle(static_cast(settings->scrollbarStyle.value())); + } } auto hr = CreateCoreWebView2EnvironmentWithOptions( @@ -66,23 +95,71 @@ namespace flutter_inappwebview_plugin if (succeededOrLog(result)) { environment_ = std::move(environment); - auto hr = environment_->CreateCoreWebView2Controller(hwnd, Callback( - [this, completionHandler](HRESULT result, wil::com_ptr controller) -> HRESULT + auto add_NewBrowserVersionAvailable_HResult = environment_->add_NewBrowserVersionAvailable(Callback( + [this](ICoreWebView2Environment* sender, IUnknown* args) { - if (succeededOrLog(result)) { - webViewController_ = std::move(controller); - webViewController_->get_CoreWebView2(&webView_); - webViewController_->put_IsVisible(false); - } - if (completionHandler) { - completionHandler(result); + if (channelDelegate) { + channelDelegate->onNewBrowserVersionAvailable(); } return S_OK; - }).Get()); + } + ).Get(), &newBrowserVersionAvailableToken_); + failedLog(add_NewBrowserVersionAvailable_HResult); + + if (auto environment5 = environment_.try_query()) { + auto add_BrowserProcessExited_HResult = environment5->add_BrowserProcessExited(Callback( + [this](ICoreWebView2Environment* sender, ICoreWebView2BrowserProcessExitedEventArgs* args) + { + if (channelDelegate) { + COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND exitKind; + std::optional kind = SUCCEEDED(args->get_BrowserProcessExitKind(&exitKind)) ? static_cast(exitKind) : std::optional{}; - if (failedAndLog(hr) && completionHandler) { - completionHandler(hr); + UINT32 pid; + std::optional processId = SUCCEEDED(args->get_BrowserProcessId(&pid)) ? static_cast(pid) : std::optional{}; + + auto browserProcessExitedDetail = std::make_shared(kind, processId); + channelDelegate->onBrowserProcessExited(std::move(browserProcessExitedDetail)); + } + return S_OK; + } + ).Get(), &browserProcessExitedToken_); + failedLog(add_BrowserProcessExited_HResult); } + + if (auto environment8 = environment_.try_query()) { + auto add_ProcessInfosChanged_HResult = environment8->add_ProcessInfosChanged(Callback( + [this, environment8](ICoreWebView2Environment* sender, IUnknown* args) + { + if (!environment_) { + return S_OK; + } + if (auto environment13 = environment_.try_query()) { + auto hr = environment13->GetProcessExtendedInfos(Callback( + [this](HRESULT error, wil::com_ptr processCollection) -> HRESULT + { + if (succeededOrLog(error) && processCollection) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(processCollection); + channelDelegate->onProcessInfosChanged(std::move(browserProcessInfosChangedDetail)); + } + return S_OK; + }).Get()); + + if (succeededOrLog(hr)) { + return S_OK; + } + } + wil::com_ptr processCollection; + if (channelDelegate && succeededOrLog(environment8->GetProcessInfos(&processCollection))) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(processCollection); + channelDelegate->onProcessInfosChanged(std::move(browserProcessInfosChangedDetail)); + } + return S_OK; + } + ).Get(), &processInfosChangedToken_); + failedLog(add_ProcessInfosChanged_HResult); + } + + completionHandler(S_OK); } else if (completionHandler) { completionHandler(result); @@ -95,8 +172,154 @@ namespace flutter_inappwebview_plugin } } + void WebViewEnvironment::useTempWebView(const std::function, wil::com_ptr)> completionHandler) const + { + auto hwnd = plugin->webViewEnvironmentManager->getHWND(); + if (!hwnd) { + if (completionHandler) { + completionHandler(nullptr, nullptr); + } + return; + } + + auto hr = environment_->CreateCoreWebView2Controller(hwnd, Callback( + [this, completionHandler](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (succeededOrLog(result)) { + controller->put_IsVisible(false); + + wil::com_ptr webView_; + controller->get_CoreWebView2(&webView_); + + if (completionHandler) { + completionHandler(std::move(controller), std::move(webView_)); + } + } + else if (completionHandler) { + completionHandler(nullptr, nullptr); + } + return S_OK; + }).Get()); + + if (failedAndLog(hr) && completionHandler) { + completionHandler(nullptr, nullptr); + } + } + + bool WebViewEnvironment::isInterfaceSupported(const std::string& interfaceName) const + { + if (!environment_) { + return false; + } + + if (starts_with(interfaceName, std::string{ "ICoreWebView2Environment" })) { + switch (string_hash(interfaceName)) { + case string_hash("ICoreWebView2Environment"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment2"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment3"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment4"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment5"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment6"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment7"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment8"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment9"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment10"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment11"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment12"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment13"): + return environment_.try_query() != nullptr; + case string_hash("ICoreWebView2Environment14"): + return environment_.try_query() != nullptr; + default: + return false; + } + } + + return false; + } + + void WebViewEnvironment::getProcessInfos(const std::function>)> completionHandler) const + { + if (!environment_) { + if (completionHandler) { + completionHandler({}); + } + return; + } + + if (auto environment13 = environment_.try_query()) { + auto hr = environment13->GetProcessExtendedInfos(Callback( + [completionHandler](HRESULT error, wil::com_ptr processCollection) -> HRESULT + { + std::vector> processInfos = {}; + if (succeededOrLog(error) && processCollection) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessExtendedInfoCollection(processCollection); + processInfos = browserProcessInfosChangedDetail->infos; + } + if (completionHandler) { + completionHandler(processInfos); + } + return S_OK; + }).Get()); + + if (succeededOrLog(hr)) { + return; + } + } + std::vector> processInfos = {}; + if (auto environment8 = environment_.try_query()) { + wil::com_ptr processCollection; + if (succeededOrLog(environment8->GetProcessInfos(&processCollection))) { + auto browserProcessInfosChangedDetail = BrowserProcessInfosChangedDetail::fromICoreWebView2ProcessInfoCollection(processCollection); + processInfos = browserProcessInfosChangedDetail->infos; + } + } + + if (completionHandler) { + completionHandler(processInfos); + } + } + + std::optional WebViewEnvironment::getFailureReportFolderPath() const + { + if (!environment_) { + return std::optional{}; + } + + if (auto environment11 = environment_.try_query()) { + wil::unique_cotaskmem_string failureReportFolderPath; + if (succeededOrLog(environment11->get_FailureReportFolderPath(&failureReportFolderPath))) { + return wide_to_utf8(failureReportFolderPath.get()); + } + } + + return std::optional{}; + } + WebViewEnvironment::~WebViewEnvironment() { debugLog("dealloc WebViewEnvironment"); + if (environment_) { + environment_->remove_NewBrowserVersionAvailable(newBrowserVersionAvailableToken_); + if (auto environment5 = environment_.try_query()) { + environment5->remove_BrowserProcessExited(browserProcessExitedToken_); + } + if (auto environment8 = environment_.try_query()) { + environment8->remove_ProcessInfosChanged(processInfosChangedToken_); + } + } + environment_ = nullptr; } } diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h index 615442d4b..7ac937993 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment.h @@ -2,6 +2,8 @@ #define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_H_ #include +#include +#include #include #include @@ -30,18 +32,17 @@ namespace flutter_inappwebview_plugin { return environment_; } - wil::com_ptr getWebViewController() - { - return webViewController_; - } - wil::com_ptr getWebView() - { - return webView_; - } + // without using a "temp" ICoreWebView2 for CookieManager and other possible usage, the onBrowserProcessExited event will never be called + void useTempWebView(const std::function, wil::com_ptr)> completionHandler) const; + bool isInterfaceSupported(const std::string& interfaceName) const; + void getProcessInfos(const std::function>)> completionHandler) const; + std::optional getFailureReportFolderPath() const; + private: wil::com_ptr environment_; - wil::com_ptr webViewController_; - wil::com_ptr webView_; + EventRegistrationToken processInfosChangedToken_ = { 0 }; + EventRegistrationToken browserProcessExitedToken_ = { 0 }; + EventRegistrationToken newBrowserVersionAvailableToken_ = { 0 }; WNDCLASS windowClass_ = {}; }; } diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp index d5b9e4d22..d8546427d 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.cpp @@ -2,6 +2,7 @@ #include "../utils/log.h" #include "../utils/strconv.h" #include "../utils/string.h" +#include "../utils/vector.h" #include "webview_environment.h" #include "webview_environment_channel_delegate.h" @@ -21,7 +22,7 @@ namespace flutter_inappwebview_plugin return; } - // auto& arguments = std::get(*method_call.arguments()); + auto& arguments = std::get(*method_call.arguments()); auto& methodName = method_call.method_name(); if (string_equals(methodName, "dispose")) { @@ -34,11 +35,53 @@ namespace flutter_inappwebview_plugin } result->Success(); } + else if (string_equals(methodName, "isInterfaceSupported")) { + auto interfaceName = get_fl_map_value(arguments, "interface"); + result->Success(webViewEnvironment->isInterfaceSupported(interfaceName)); + } + else if (string_equals(methodName, "getProcessInfos")) { + auto result_ = std::shared_ptr>(std::move(result)); + webViewEnvironment->getProcessInfos([result_ = std::move(result_)](std::vector> processInfos) + { + result_->Success(make_fl_value(functional_map(processInfos, [](const std::shared_ptr& info) { return info->toEncodableMap(); }))); + }); + } + else if (string_equals(methodName, "getFailureReportFolderPath")) { + result->Success(make_fl_value(webViewEnvironment->getFailureReportFolderPath())); + } else { result->NotImplemented(); } } + void WebViewEnvironmentChannelDelegate::onNewBrowserVersionAvailable() const + { + if (!channel) { + return; + } + channel->InvokeMethod("onNewBrowserVersionAvailable", nullptr); + } + + void WebViewEnvironmentChannelDelegate::onBrowserProcessExited(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onBrowserProcessExited", std::move(arguments)); + } + + void WebViewEnvironmentChannelDelegate::onProcessInfosChanged(std::shared_ptr detail) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(detail->toEncodableMap()); + channel->InvokeMethod("onProcessInfosChanged", std::move(arguments)); + } + WebViewEnvironmentChannelDelegate::~WebViewEnvironmentChannelDelegate() { debugLog("dealloc WebViewEnvironmentChannelDelegate"); diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h index b98e67abf..c51c18f05 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_channel_delegate.h @@ -1,9 +1,12 @@ #ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ #define FLUTTER_INAPPWEBVIEW_PLUGIN_WEBVIEW_ENVIRONMENT_CHANNEL_DELEGATE_H_ -#include "../types/channel_delegate.h" #include +#include "../types/browser_process_exited_detail.h" +#include "../types/browser_process_infos_changed_detail.h" +#include "../types/channel_delegate.h" + namespace flutter_inappwebview_plugin { class WebViewEnvironment; @@ -19,6 +22,10 @@ namespace flutter_inappwebview_plugin void HandleMethodCall( const flutter::MethodCall& method_call, std::unique_ptr> result); + + void onNewBrowserVersionAvailable() const; + void onBrowserProcessExited(std::shared_ptr detail) const; + void onProcessInfosChanged(std::shared_ptr detail) const; }; } diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp index 7667f6003..95802937a 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.cpp @@ -10,7 +10,14 @@ namespace flutter_inappwebview_plugin allowSingleSignOnUsingOSPrimaryAccount(get_optional_fl_map_value(map, "allowSingleSignOnUsingOSPrimaryAccount")), language(get_optional_fl_map_value(map, "language")), targetCompatibleBrowserVersion(get_optional_fl_map_value(map, "targetCompatibleBrowserVersion")), - customSchemeRegistrations(functional_map(get_optional_fl_map_value(map, "customSchemeRegistrations"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })) + customSchemeRegistrations(functional_map(get_optional_fl_map_value(map, "customSchemeRegistrations"), [](const flutter::EncodableValue& m) { return std::make_shared(std::get(m)); })), + exclusiveUserDataFolderAccess(get_optional_fl_map_value(map, "exclusiveUserDataFolderAccess")), + isCustomCrashReportingEnabled(get_optional_fl_map_value(map, "isCustomCrashReportingEnabled")), + enableTrackingPrevention(get_optional_fl_map_value(map, "enableTrackingPrevention")), + areBrowserExtensionsEnabled(get_optional_fl_map_value(map, "areBrowserExtensionsEnabled")), + channelSearchKind(get_optional_fl_map_value(map, "channelSearchKind")), + releaseChannels(get_optional_fl_map_value(map, "releaseChannels")), + scrollbarStyle(get_optional_fl_map_value(map, "scrollbarStyle")) {} flutter::EncodableMap WebViewEnvironmentSettings::toEncodableMap() const @@ -22,7 +29,14 @@ namespace flutter_inappwebview_plugin {"allowSingleSignOnUsingOSPrimaryAccount", make_fl_value(allowSingleSignOnUsingOSPrimaryAccount)}, {"language", make_fl_value(language)}, {"targetCompatibleBrowserVersion", make_fl_value(targetCompatibleBrowserVersion)}, - {"customSchemeRegistrations", make_fl_value(functional_map(customSchemeRegistrations, [](const std::shared_ptr& item) { return item->toEncodableMap(); }))} + {"customSchemeRegistrations", make_fl_value(functional_map(customSchemeRegistrations, [](const std::shared_ptr& item) { return item->toEncodableMap(); }))}, + {"exclusiveUserDataFolderAccess", make_fl_value(exclusiveUserDataFolderAccess)}, + {"isCustomCrashReportingEnabled", make_fl_value(isCustomCrashReportingEnabled)}, + {"enableTrackingPrevention", make_fl_value(enableTrackingPrevention)}, + {"areBrowserExtensionsEnabled", make_fl_value(areBrowserExtensionsEnabled)}, + {"channelSearchKind", make_fl_value(channelSearchKind)}, + {"releaseChannels", make_fl_value(releaseChannels)}, + {"scrollbarStyle", make_fl_value(scrollbarStyle)} }; } diff --git a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h index 84314aac6..bef41b343 100644 --- a/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h +++ b/flutter_inappwebview_windows/windows/webview_environment/webview_environment_settings.h @@ -20,6 +20,13 @@ namespace flutter_inappwebview_plugin const std::optional language; const std::optional targetCompatibleBrowserVersion; const std::optional>> customSchemeRegistrations; + const std::optional exclusiveUserDataFolderAccess; + const std::optional isCustomCrashReportingEnabled; + const std::optional enableTrackingPrevention; + const std::optional areBrowserExtensionsEnabled; + const std::optional channelSearchKind; + const std::optional releaseChannels; + const std::optional scrollbarStyle; WebViewEnvironmentSettings() = default; WebViewEnvironmentSettings(const flutter::EncodableMap& map); diff --git a/test_node_server/index.js b/test_node_server/index.js index ad68fb3a7..72c0a8248 100755 --- a/test_node_server/index.js +++ b/test_node_server/index.js @@ -12,6 +12,7 @@ // - Overwrite certificate.pfx to example/test_assets/certificate.pfx const express = require('express'); const http = require('http'); +const net = require('net'); const https = require('https'); const cors = require('cors'); const auth = require('basic-auth'); @@ -224,19 +225,41 @@ app.get("/test-download-file", (req, res) => { app.listen(8082) // Proxy server -http.createServer(function (req, res) { - res.setHeader('Content-type', 'text/html'); - res.write(` - - - - -

Proxy Works

-

${req.url}

-

${req.method}

-

${JSON.stringify(req.headers)}

- - - `); - res.end(); -}).listen(8083); \ No newline at end of file +// Create an HTTP tunneling proxy +const proxy = http.createServer((req, res) => { + console.log('proxy response', req.url); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(` + + + + +

Proxy Works

+

${req.url}

+

${req.method}

+

${JSON.stringify(req.headers)}

+ + + `); +}); +proxy.on('connect', (req, clientSocket, head) => { + console.log('proxy connect request'); + // Connect to an origin server + // const { port, hostname } = new URL(`http://${req.url}`); + const { port, hostname } = new URL(`http://127.0.0.1:8083`); + const serverSocket = net.connect(port || 80, hostname, () => { + clientSocket.write('HTTP/1.1 200 Connection Established\r\n' + + 'Proxy-agent: Node.js-Proxy\r\n' + + '\r\n'); + serverSocket.write(head); + serverSocket.pipe(clientSocket); + clientSocket.pipe(serverSocket); + }); +}); +proxy.listen(8083, null, () => { + console.log('proxy server listening on port 8083'); +}); + +process.on('uncaughtException', function (err) { + console.error(err); +}); \ No newline at end of file