Commit 02c3de92 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added HeadlessInAppWebView class, added close, addMenuItem and addMenuItems...

added HeadlessInAppWebView class, added close, addMenuItem and addMenuItems methods to ChromeSafariBrowser, added ChromeSafariBrowserMenuItem class, fixed InAppWebView.channel null exception on android, fix #305, fix #245, fix #299
parent f9ed06cc
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</value>
</option>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">
<indentOptions> <indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
......
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<state> <state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> <option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state> </state>
</component> </component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
<component name="MarkdownNavigatorHistory">
<PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</PasteImageHistory>
<CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</CopyImageHistory>
<PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" />
<TableToJsonHistory>
<entries />
</TableToJsonHistory>
<TableSortHistory>
<entries />
</TableSortHistory>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="MarkdownProjectSettings" wasCopied="false"> <component name="FlexmarkProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false"> <FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider> <PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" /> <provider providerId="com.vladsch.md.nav.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider> </PanelProvider>
</PreviewSettings> </PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" emojiShortcuts="1" emojiImages="0"> <ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions> <PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" /> <option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" /> <option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="true" />
<option name="DEFINITIONS" value="false" />
<option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" /> <option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" /> <option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="HARDWRAPS" value="false" />
<option name="HTML_DEEP_PARSER" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" /> <option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" /> <option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" /> <option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" /> <option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="true" /> <option name="WIKILINKS" value="true" />
</PegdownExtensions> </PegdownExtensions>
<ParserOptions> <ParserOptions>
<option name="ADMONITION_EXT" value="false" />
<option name="ATTRIBUTES_EXT" value="false" />
<option name="COMMONMARK_LISTS" value="true" /> <option name="COMMONMARK_LISTS" value="true" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" /> <option name="EMOJI_SHORTCUTS" value="true" />
<option name="ENUMERATED_REFERENCES_EXT" value="false" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
<option name="GFM_TABLE_RENDERING" value="true" /> <option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_LISTS" value="false" />
<option name="GITHUB_WIKI_LINKS" value="true" /> <option name="GITHUB_WIKI_LINKS" value="true" />
<option name="GITLAB_EXT" value="false" /> <option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="GITLAB_MATH_EXT" value="false" />
<option name="GITLAB_MERMAID_EXT" value="false" />
<option name="HEADER_ID_NO_DUPED_DASHES" value="false" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="MACROS_EXT" value="false" />
<option name="NO_TEXT_ATTRIBUTES" value="false" />
<option name="PARSE_HTML_ANCHOR_ID" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" /> <option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions> </ParserOptions>
</ParserSettings> </ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false" addDocTypeHtml="true"> <HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="true" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0">
<GeneratorProvider> <GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" /> <provider providerId="com.vladsch.md.nav.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider> </GeneratorProvider>
<headerTop /> <headerTop />
<headerBottom /> <headerBottom />
...@@ -68,15 +47,11 @@ ...@@ -68,15 +47,11 @@
</HtmlSettings> </HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true"> <CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider> <StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" /> <provider providerId="com.vladsch.md.nav.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider> </StylesheetProvider>
<ScriptProviders /> <ScriptProviders />
<cssText /> <cssText />
<cssUriHistory /> <cssUriHistory />
</CssSettings> </CssSettings>
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetExt="" useTargetExt="false" noCssNoScripts="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component> </component>
</project> </project>
\ No newline at end of file
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" />
</component>
\ No newline at end of file
## 3.1.0
- Added `HeadlessInAppWebView` class to be able to use WebView in headless mode
- Added `close`, `addMenuItem`, `addMenuItems` methods to `ChromeSafariBrowser`
- Added `ChromeSafariBrowserMenuItem` class in order to create custom menu item for `ChromeSafariBrowser`.
- Fixed `InAppWebView.channel` null when used by `InAppBrowserActivity` on android
- Fixed iOS presentationStyle affecting only dismiss animation [#305](https://github.com/pichillilorenzo/flutter_inappwebview/issues/305)
### BREAKING CHANGES
- Renamed `InAppWebViewWidgetOptions` to `InAppWebViewGroupOptions`.
## 3.0.0 ## 3.0.0
- Added `Promise` javascript [polyfill](https://github.com/tildeio/rsvp.js) for webviews that doesn't support it for `window.flutter_inappwebview.callHandler` - Added `Promise` javascript [polyfill](https://github.com/tildeio/rsvp.js) for webviews that doesn't support it for `window.flutter_inappwebview.callHandler`
......
...@@ -68,6 +68,7 @@ First, add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](ht ...@@ -68,6 +68,7 @@ First, add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](ht
Classes: Classes:
- [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`. - [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`.
- [HeadlessInAppWebView](#headlessinappwebview-class): Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
- [InAppBrowser](#inappbrowser-class): In-App Browser using native WebView. - [InAppBrowser](#inappbrowser-class): In-App Browser using native WebView.
- [ChromeSafariBrowser](#chromesafaribrowser-class): In-App Browser using [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS. - [ChromeSafariBrowser](#chromesafaribrowser-class): In-App Browser using [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
- [InAppLocalhostServer](#inapplocalhostserver-class): This class allows you to create a simple server on `http://localhost:[port]/`. The default `port` value is `8080`. - [InAppLocalhostServer](#inapplocalhostserver-class): This class allows you to create a simple server on `http://localhost:[port]/`. The default `port` value is `8080`.
...@@ -132,7 +133,7 @@ Future main() async { ...@@ -132,7 +133,7 @@ Future main() async {
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@override @override
_MyAppState createState() => new _MyAppState(); _MyAppState createState() => new _MyAppState();
} }z
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
...@@ -177,7 +178,7 @@ class _MyAppState extends State<MyApp> { ...@@ -177,7 +178,7 @@ class _MyAppState extends State<MyApp> {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
debuggingEnabled: true, debuggingEnabled: true,
) )
...@@ -280,7 +281,7 @@ Screenshots: ...@@ -280,7 +281,7 @@ Screenshots:
* `addJavaScriptHandler({@required String handlerName, @required JavaScriptHandlerCallback callback})`: Adds a JavaScript message handler callback that listen to post messages sent from JavaScript by the handler with name `handlerName`. * `addJavaScriptHandler({@required String handlerName, @required JavaScriptHandlerCallback callback})`: Adds a JavaScript message handler callback that listen to post messages sent from JavaScript by the handler with name `handlerName`.
* `removeJavaScriptHandler({@required String handlerName})`: Removes a JavaScript message handler previously added with the `addJavaScriptHandler()` associated to `handlerName` key. * `removeJavaScriptHandler({@required String handlerName})`: Removes a JavaScript message handler previously added with the `addJavaScriptHandler()` associated to `handlerName` key.
* `takeScreenshot`: Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it. * `takeScreenshot`: Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it.
* `setOptions({@required InAppWebViewWidgetOptions options})`: Sets the WebView options with the new options and evaluates them. * `setOptions({@required InAppWebViewGroupOptions options})`: Sets the WebView options with the new options and evaluates them.
* `getOptions`: Gets the current WebView options. Returns the options with `null` value if they are not set yet. * `getOptions`: Gets the current WebView options. Returns the options with `null` value if they are not set yet.
* `getCopyBackForwardList`: Gets the `WebHistory` for this WebView. This contains the back/forward list for use in querying each item in the history stack. * `getCopyBackForwardList`: Gets the `WebHistory` for this WebView. This contains the back/forward list for use in querying each item in the history stack.
* `clearCache`: Clears all the webview's cache. * `clearCache`: Clears all the webview's cache.
...@@ -333,8 +334,8 @@ This event will be dispatched as soon as the platform (Android or iOS) is ready ...@@ -333,8 +334,8 @@ This event will be dispatched as soon as the platform (Android or iOS) is ready
``` ```
`window.flutter_inappwebview.callHandler` returns a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) `window.flutter_inappwebview.callHandler` returns a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
that can be used to get the json result returned by [JavaScriptHandlerCallback]. that can be used to get the json result returned by `JavaScriptHandlerCallback.
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. 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.
So, on the JavaScript side, to get data coming from the Dart side, you will use: So, on the JavaScript side, to get data coming from the Dart side, you will use:
```html ```html
...@@ -498,6 +499,127 @@ Event names that starts with `android` or `ios` are events platform-specific. ...@@ -498,6 +499,127 @@ Event names that starts with `android` or `ios` are events platform-specific.
* `iosOnDidCommit`: Called when the web view begins to receive web content (available only on iOS). * `iosOnDidCommit`: Called when the web view begins to receive web content (available only on iOS).
* `iosOnDidReceiveServerRedirectForProvisionalNavigation`: Called when a web view receives a server redirect (available only on iOS). * `iosOnDidReceiveServerRedirectForProvisionalNavigation`: Called when a web view receives a server redirect (available only on iOS).
### `HeadlessInAppWebView` class
Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
Remember to dispose it when you don't need it anymore.
As `InAppWebView`, it has the same options and events. Use `InAppWebViewController` to control the WebView instance.
Example:
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
HeadlessInAppWebView headlessWebView;
String url = "";
@override
void initState() {
super.initState();
headlessWebView = new HeadlessInAppWebView(
initialUrl: "https://flutter.dev/",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
),
),
onWebViewCreated: (controller) {
print('HeadlessInAppWebView created!');
},
onConsoleMessage: (controller, consoleMessage) {
print("CONSOLE MESSAGE: " + consoleMessage.message);
},
onLoadStart: (controller, url) async {
print("onLoadStart $url");
setState(() {
this.url = url;
});
},
onLoadStop: (controller, url) async {
print("onLoadStop $url");
setState(() {
this.url = url;
});
},
onUpdateVisitedHistory: (InAppWebViewController controller, String url, bool androidIsReload) {
print("onUpdateVisitedHistory $url");
setState(() {
this.url = url;
});
},
);
}
@override
void dispose() {
super.dispose();
headlessWebView.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"HeadlessInAppWebView",
)),
drawer: myDrawer(context: context),
body: SafeArea(
child: Column(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
),
Center(
child: RaisedButton(
onPressed: () async {
await headlessWebView.dispose();
await headlessWebView.run();
},
child: Text("Run HeadlessInAppWebView")),
),
Center(
child: RaisedButton(
onPressed: () async {
try {
await headlessWebView.webViewController.evaluateJavascript(source: """console.log('Here is the message!');""");
} on MissingPluginException catch(e) {
print("HeadlessInAppWebView is not running. Click on \"Run HeadlessInAppWebView\"!");
}
},
child: Text("Send console.log message")),
),
Center(
child: RaisedButton(
onPressed: () {
headlessWebView.dispose();
},
child: Text("Dispose HeadlessInAppWebView")),
)
])
)
);
}
}
```
### `InAppBrowser` class ### `InAppBrowser` class
In-App Browser using native WebView. In-App Browser using native WebView.
...@@ -596,7 +718,7 @@ class _MyAppState extends State<MyApp> { ...@@ -596,7 +718,7 @@ class _MyAppState extends State<MyApp> {
widget.browser.openFile( widget.browser.openFile(
assetFilePath: "assets/index.html", assetFilePath: "assets/index.html",
options: InAppBrowserClassOptions( options: InAppBrowserClassOptions(
inAppWebViewWidgetOptions: InAppWebViewWidgetOptions( inAppWebViewGroupOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true, useShouldOverrideUrlLoading: true,
useOnLoadResource: true, useOnLoadResource: true,
...@@ -744,6 +866,16 @@ class _MyAppState extends State<MyApp> { ...@@ -744,6 +866,16 @@ class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
widget.browser.addMenuItem(new ChromeSafariBrowserMenuItem(id: 1, label: 'Custom item menu 1', action: (url, title) {
print('Custom item menu 1 clicked!');
print(url);
print(title);
}));
widget.browser.addMenuItem(new ChromeSafariBrowserMenuItem(id: 2, label: 'Custom item menu 2', action: (url, title) {
print('Custom item menu 2 clicked!');
print(url);
print(title);
}));
super.initState(); super.initState();
} }
...@@ -785,6 +917,9 @@ Screenshots: ...@@ -785,6 +917,9 @@ Screenshots:
* `open({@required String url, ChromeSafariBrowserClassOptions options, Map<String, String> headersFallback = const {}, InAppBrowserClassOptions optionsFallback})`: Opens an `url` in a new `ChromeSafariBrowser` instance. * `open({@required String url, ChromeSafariBrowserClassOptions options, Map<String, String> headersFallback = const {}, InAppBrowserClassOptions optionsFallback})`: Opens an `url` in a new `ChromeSafariBrowser` instance.
* `isOpened`: Returns `true` if the `ChromeSafariBrowser` instance is opened, otherwise `false`. * `isOpened`: Returns `true` if the `ChromeSafariBrowser` instance is opened, otherwise `false`.
* `close`: Closes the `ChromeSafariBrowser` instance.
* `addMenuItem`: Adds a `ChromeSafariBrowserMenuItem` to the menu.
* `addMenuItems`: Adds a list of `ChromeSafariBrowserMenuItem` to the menu.
#### `ChromeSafariBrowser` options #### `ChromeSafariBrowser` options
...@@ -846,7 +981,7 @@ Future main() async { ...@@ -846,7 +981,7 @@ Future main() async {
child: InAppWebView( child: InAppWebView(
initialUrl: "http://localhost:8080/assets/index.html", initialUrl: "http://localhost:8080/assets/index.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
inAppWebViewOptions: InAppWebViewOptions( inAppWebViewOptions: InAppWebViewOptions(
debuggingEnabled: true, debuggingEnabled: true,
) )
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutter_inappwebview"> package="com.pichillilorenzo.flutter_inappwebview">
<application> <application>
<activity android:theme="@style/AppTheme" android:name="com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity" android:configChanges="orientation|screenSize"></activity> <activity android:theme="@style/AppTheme" android:name="com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity" android:configChanges="orientation|screenSize"></activity>
<activity android:theme="@style/ThemeTransparent" android:name="com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ChromeCustomTabsActivity" android:configChanges="orientation|screenSize"></activity> <activity android:theme="@style/ThemeTransparent" android:name="com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ChromeCustomTabsActivity" android:configChanges="orientation|screenSize"></activity>
<receiver android:name="com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ActionBroadcastReceiver" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
package com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
public class ActionBroadcastReceiver extends BroadcastReceiver {
protected static final String LOG_TAG = "ActionBroadcastReceiver";
public static final String KEY_ACTION_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_ID";
public static final String KEY_ACTION_UUID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_UUID";
public static final String KEY_URL_TITLE = "android.intent.extra.SUBJECT";
@Override
public void onReceive(Context context, Intent intent) {
String url = intent.getDataString();
if (url != null) {
Bundle b = intent.getExtras();
String uuid = b.getString(KEY_ACTION_UUID);
int id = b.getInt(KEY_ACTION_ID);
String title = b.getString(KEY_URL_TITLE);
MethodChannel channel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_chromesafaribrowser_" + uuid);
Map<String, Object> obj = new HashMap<>();
obj.put("url", url);
obj.put("title", title);
obj.put("id", id);
channel.invokeMethod("onChromeSafariBrowserMenuItemActionPerform", obj);
}
}
}
package com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs; package com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import androidx.browser.customtabs.CustomTabsCallback; import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService; import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession; import androidx.browser.customtabs.CustomTabsSession;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
...@@ -52,6 +55,8 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel. ...@@ -52,6 +55,8 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
options = new ChromeCustomTabsOptions(); options = new ChromeCustomTabsOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options")); options.parse((HashMap<String, Object>) b.getSerializable("options"));
final List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) b.getSerializable("menuItemList");
final ChromeCustomTabsActivity chromeCustomTabsActivity = this; final ChromeCustomTabsActivity chromeCustomTabsActivity = this;
customTabActivityHelper = new CustomTabActivityHelper(); customTabActivityHelper = new CustomTabActivityHelper();
...@@ -63,18 +68,17 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel. ...@@ -63,18 +68,17 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
customTabActivityHelper.mayLaunchUrl(uri, null, null); customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new CustomTabsIntent.Builder(customTabsSession); builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs(menuItemList);
CustomTabsIntent customTabsIntent = builder.build(); CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabs(customTabsIntent); prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(chromeCustomTabsActivity, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE); CustomTabActivityHelper.openCustomTab(chromeCustomTabsActivity, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
} }
@Override @Override
public void onCustomTabsDisconnected() { public void onCustomTabsDisconnected() {
customTabsSession = null; chromeCustomTabsActivity.close();
finish();
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
channel.invokeMethod("onChromeSafariBrowserClosed", obj);
} }
}); });
...@@ -122,12 +126,23 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel. ...@@ -122,12 +126,23 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
@Override @Override
public void onMethodCall(final MethodCall call, final MethodChannel.Result result) { public void onMethodCall(final MethodCall call, final MethodChannel.Result result) {
switch (call.method) { switch (call.method) {
case "close":
this.onStop();
this.onDestroy();
this.close();
// https://stackoverflow.com/a/41596629/4637638
Intent myIntent = new Intent(Shared.activity, Shared.activity.getClass());
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Shared.activity.startActivity(myIntent);
break;
default: default:
result.notImplemented(); result.notImplemented();
} }
} }
private void prepareCustomTabs(CustomTabsIntent customTabsIntent) { private void prepareCustomTabs(List<HashMap<String, Object>> menuItemList) {
if (options.addDefaultShareMenuItem) if (options.addDefaultShareMenuItem)
builder.addDefaultShareMenuItem(); builder.addDefaultShareMenuItem();
...@@ -141,6 +156,14 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel. ...@@ -141,6 +156,14 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
builder.setInstantAppsEnabled(options.instantAppsEnabled); builder.setInstantAppsEnabled(options.instantAppsEnabled);
for (HashMap<String, Object> menuItem : menuItemList) {
int id = (int) menuItem.get("id");
String label = (String) menuItem.get("label");
builder.addMenuItem(label, createPendingIntent(id));
}
}
private void prepareCustomTabsIntent(CustomTabsIntent customTabsIntent) {
if (options.packageName != null) if (options.packageName != null)
customTabsIntent.intent.setPackage(options.packageName); customTabsIntent.intent.setPackage(options.packageName);
else else
...@@ -165,12 +188,27 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel. ...@@ -165,12 +188,27 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) { if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
customTabsSession = null; close();
finish();
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppWebViewFlutterPlugin.inAppBrowserManager.channel.invokeMethod("onChromeSafariBrowserClosed", obj);
} }
} }
public void close() {
customTabsSession = null;
finish();
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
channel.invokeMethod("onChromeSafariBrowserClosed", obj);
}
private PendingIntent createPendingIntent(int actionSourceId) {
Intent actionIntent = new Intent(this, ActionBroadcastReceiver.class);
Bundle extras = new Bundle();
extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_UUID, uuid);
actionIntent.putExtras(extras);
return PendingIntent.getBroadcast(
this, actionSourceId, actionIntent, 0);
}
} }
...@@ -7,9 +7,12 @@ import android.util.Log; ...@@ -7,9 +7,12 @@ import android.util.Log;
import com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ChromeCustomTabsActivity; import com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ChromeCustomTabsActivity;
import com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.CustomTabActivityHelper; import com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.CustomTabActivityHelper;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserOptions;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
...@@ -40,7 +43,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -40,7 +43,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
String uuidFallback = (String) call.argument("uuidFallback"); String uuidFallback = (String) call.argument("uuidFallback");
Map<String, String> headersFallback = (Map<String, String>) call.argument("headersFallback"); Map<String, String> headersFallback = (Map<String, String>) call.argument("headersFallback");
HashMap<String, Object> optionsFallback = (HashMap<String, Object>) call.argument("optionsFallback"); HashMap<String, Object> optionsFallback = (HashMap<String, Object>) call.argument("optionsFallback");
open(activity, uuid, url, options, uuidFallback, headersFallback, optionsFallback, result); List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
open(activity, uuid, url, options, uuidFallback, headersFallback, optionsFallback, menuItemList, result);
} }
break; break;
default: default:
...@@ -48,7 +52,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -48,7 +52,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
} }
} }
public void open(Activity activity, String uuid, String url, HashMap<String, Object> options, String uuidFallback, Map<String, String> headersFallback, HashMap<String, Object> optionsFallback, MethodChannel.Result result) { public void open(Activity activity, String uuid, String url, HashMap<String, Object> options, String uuidFallback,
Map<String, String> headersFallback, HashMap<String, Object> optionsFallback, List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
Intent intent = null; Intent intent = null;
Bundle extras = new Bundle(); Bundle extras = new Bundle();
...@@ -58,6 +63,7 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -58,6 +63,7 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
extras.putString("uuid", uuid); extras.putString("uuid", uuid);
extras.putSerializable("options", options); extras.putSerializable("options", options);
extras.putSerializable("headers", (Serializable) headersFallback); extras.putSerializable("headers", (Serializable) headersFallback);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
if (CustomTabActivityHelper.isAvailable(activity)) { if (CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, ChromeCustomTabsActivity.class); intent = new Intent(activity, ChromeCustomTabsActivity.class);
......
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package com.pichillilorenzo.flutter_inappwebview;
import android.app.Activity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.FlutterWebView;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
/**
* InAppBrowserManager
*/
public class HeadlessInAppWebViewManager implements MethodChannel.MethodCallHandler {
public MethodChannel channel;
protected static final String LOG_TAG = "HeadlessInAppWebViewManager";
Map<String, FlutterWebView> flutterWebViews = new HashMap<>();
public HeadlessInAppWebViewManager(BinaryMessenger messenger) {
channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_headless_inappwebview");
channel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(final MethodCall call, final Result result) {
final Activity activity = Shared.activity;
final String uuid = (String) call.argument("uuid");
switch (call.method) {
case "createHeadlessWebView":
{
HashMap<String, Object> params = (HashMap<String, Object>) call.argument("params");
createHeadlessWebView(activity, uuid, params);
}
result.success(true);
break;
case "disposeHeadlessWebView":
disposeHeadlessWebView(uuid);
result.success(true);
break;
default:
result.notImplemented();
}
}
public void createHeadlessWebView(Activity activity, String uuid, HashMap<String, Object> params) {
FlutterWebView flutterWebView = new FlutterWebView(Shared.messenger, activity, uuid, params, null);
flutterWebViews.put(uuid, flutterWebView);
}
public void disposeHeadlessWebView(String uuid) {
if (flutterWebViews.containsKey(uuid)) {
flutterWebViews.get(uuid).dispose();
flutterWebViews.remove(uuid);
}
}
public void dispose() {
channel.setMethodCallHandler(null);
}
}
package com.pichillilorenzo.flutter_inappwebview; package com.pichillilorenzo.flutter_inappwebview.InAppBrowser;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
...@@ -26,6 +26,8 @@ import androidx.appcompat.app.AppCompatActivity; ...@@ -26,6 +26,8 @@ import androidx.appcompat.app.AppCompatActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
...@@ -65,6 +67,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -65,6 +67,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
webView = findViewById(R.id.webView); webView = findViewById(R.id.webView);
webView.inAppBrowserActivity = this; webView.inAppBrowserActivity = this;
webView.channel = channel;
fromActivity = b.getString("fromActivity"); fromActivity = b.getString("fromActivity");
......
package com.pichillilorenzo.flutter_inappwebview; package com.pichillilorenzo.flutter_inappwebview.InAppBrowser;
import com.pichillilorenzo.flutter_inappwebview.Options;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
......
...@@ -32,6 +32,8 @@ import android.os.Bundle; ...@@ -32,6 +32,8 @@ import android.os.Bundle;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.util.Log; import android.util.Log;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
......
package com.pichillilorenzo.flutter_inappwebview; package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
...@@ -14,6 +14,8 @@ import android.webkit.WebViewClient; ...@@ -14,6 +14,8 @@ import android.webkit.WebViewClient;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.DisplayListenerProxy; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.DisplayListenerProxy;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
...@@ -37,7 +39,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -37,7 +39,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
public InAppWebView webView; public InAppWebView webView;
public final MethodChannel channel; public final MethodChannel channel;
public FlutterWebView(BinaryMessenger messenger, final Context context, int id, HashMap<String, Object> params, View containerView) { public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, HashMap<String, Object> params, View containerView) {
channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id); channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id);
channel.setMethodCallHandler(this); channel.setMethodCallHandler(this);
...@@ -93,6 +95,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -93,6 +95,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
} }
else else
webView.loadUrl(initialUrl, initialHeaders); webView.loadUrl(initialUrl, initialHeaders);
if (containerView == null && id instanceof String) {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", id);
channel.invokeMethod("onHeadlessWebViewCreated", obj);
}
} }
@Override @Override
......
package com.pichillilorenzo.flutter_inappwebview; package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.FlutterWebView;
import java.util.HashMap; import java.util.HashMap;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory; import io.flutter.plugin.platform.PlatformViewFactory;
......
...@@ -5,19 +5,16 @@ import android.graphics.Bitmap; ...@@ -5,19 +5,16 @@ import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.print.PrintAttributes; import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter; import android.print.PrintDocumentAdapter;
import android.print.PrintManager; import android.print.PrintManager;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewParent;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -27,17 +24,12 @@ import android.webkit.WebSettings; ...@@ -27,17 +24,12 @@ import android.webkit.WebSettings;
import android.webkit.WebStorage; import android.webkit.WebStorage;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.PopupMenu;
import android.view.ActionMode;
import android.webkit.WebView;
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlocker; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlocker;
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerAction; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerAction;
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerHandler; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerHandler;
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerTrigger; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerTrigger;
import com.pichillilorenzo.flutter_inappwebview.FlutterWebView; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.Util;
...@@ -62,7 +54,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -62,7 +54,7 @@ final public class InAppWebView extends InputAwareWebView {
public InAppBrowserActivity inAppBrowserActivity; public InAppBrowserActivity inAppBrowserActivity;
public FlutterWebView flutterWebView; public FlutterWebView flutterWebView;
public MethodChannel channel; public MethodChannel channel;
public int id; public Object id;
public InAppWebViewClient inAppWebViewClient; public InAppWebViewClient inAppWebViewClient;
public InAppWebViewChromeClient inAppWebViewChromeClient; public InAppWebViewChromeClient inAppWebViewChromeClient;
public InAppWebViewOptions options; public InAppWebViewOptions options;
...@@ -527,7 +519,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -527,7 +519,7 @@ final public class InAppWebView extends InputAwareWebView {
super(context, attrs, defaultStyle); super(context, attrs, defaultStyle);
} }
public InAppWebView(Context context, Object obj, int id, InAppWebViewOptions options, View containerView) { public InAppWebView(Context context, Object obj, Object id, InAppWebViewOptions options, View containerView) {
super(context, containerView); super(context, containerView);
if (obj instanceof InAppBrowserActivity) if (obj instanceof InAppBrowserActivity)
this.inAppBrowserActivity = (InAppBrowserActivity) obj; this.inAppBrowserActivity = (InAppBrowserActivity) obj;
...@@ -891,7 +883,8 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -891,7 +883,8 @@ final public class InAppWebView extends InputAwareWebView {
} }
public void takeScreenshot(final MethodChannel.Result result) { public void takeScreenshot(final MethodChannel.Result result) {
post(new Runnable() { Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
int height = (int) (getContentHeight() * scale + 0.5); int height = (int) (getContentHeight() * scale + 0.5);
...@@ -1199,7 +1192,8 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -1199,7 +1192,8 @@ final public class InAppWebView extends InputAwareWebView {
scriptToInject = String.format(jsWrapper, jsonSourceString); scriptToInject = String.format(jsWrapper, jsonSourceString);
} }
final String finalScriptToInject = scriptToInject; final String finalScriptToInject = scriptToInject;
post(new Runnable() { Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
......
...@@ -27,8 +27,7 @@ import android.widget.LinearLayout; ...@@ -27,8 +27,7 @@ import android.widget.LinearLayout;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import com.pichillilorenzo.flutter_inappwebview.FlutterWebView; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
......
...@@ -24,9 +24,7 @@ import androidx.annotation.RequiresApi; ...@@ -24,9 +24,7 @@ import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.Credential; import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.Credential;
import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.CredentialDatabase; import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.CredentialDatabase;
import com.pichillilorenzo.flutter_inappwebview.FlutterWebView; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.Util;
......
...@@ -6,6 +6,8 @@ import android.net.Uri; ...@@ -6,6 +6,8 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.FlutterWebViewFactory;
import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
...@@ -19,6 +21,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { ...@@ -19,6 +21,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
protected static final String LOG_TAG = "InAppWebViewFlutterPL"; protected static final String LOG_TAG = "InAppWebViewFlutterPL";
public static InAppBrowserManager inAppBrowserManager; public static InAppBrowserManager inAppBrowserManager;
public static HeadlessInAppWebViewManager headlessInAppWebViewManager;
public static ChromeSafariBrowserManager chromeSafariBrowserManager; public static ChromeSafariBrowserManager chromeSafariBrowserManager;
public static InAppWebViewStatic inAppWebViewStatic; public static InAppWebViewStatic inAppWebViewStatic;
public static MyCookieManager myCookieManager; public static MyCookieManager myCookieManager;
...@@ -49,6 +52,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { ...@@ -49,6 +52,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
Shared.messenger = messenger; Shared.messenger = messenger;
inAppBrowserManager = new InAppBrowserManager(messenger); inAppBrowserManager = new InAppBrowserManager(messenger);
headlessInAppWebViewManager = new HeadlessInAppWebViewManager(messenger);
chromeSafariBrowserManager = new ChromeSafariBrowserManager(messenger); chromeSafariBrowserManager = new ChromeSafariBrowserManager(messenger);
platformViewRegistry.registerViewFactory( platformViewRegistry.registerViewFactory(
...@@ -67,6 +71,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { ...@@ -67,6 +71,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
inAppBrowserManager.dispose(); inAppBrowserManager.dispose();
inAppBrowserManager = null; inAppBrowserManager = null;
} }
if (headlessInAppWebViewManager != null) {
headlessInAppWebViewManager.dispose();
headlessInAppWebViewManager = null;
}
if (chromeSafariBrowserManager != null) { if (chromeSafariBrowserManager != null) {
chromeSafariBrowserManager.dispose(); chromeSafariBrowserManager.dispose();
chromeSafariBrowserManager = null; chromeSafariBrowserManager = null;
......
...@@ -7,6 +7,8 @@ import android.util.Log; ...@@ -7,6 +7,8 @@ import android.util.Log;
import android.webkit.JavascriptInterface; import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.FlutterWebView;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;
import java.util.HashMap; import java.util.HashMap;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clickable="true" android:clickable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
tools:context=".InAppBrowserActivity" tools:context=".InAppBrowser.InAppBrowserActivity"
android:focusable="true"> android:focusable="true">
<com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView <com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
xmlns:appcompat="http://schemas.android.com/apk/res-auto" xmlns:appcompat="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".InAppBrowserActivity"> tools:context=".InAppBrowser.InAppBrowserActivity">
<item <item
android:id="@+id/action_go_back" android:id="@+id/action_go_back"
......
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-09 04:59:17.693618","version":"1.17.0"} {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-11 02:42:13.697935","version":"1.17.0"}
\ No newline at end of file \ No newline at end of file
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"
...@@ -35,6 +35,16 @@ class _ChromeSafariBrowserExampleScreenState ...@@ -35,6 +35,16 @@ class _ChromeSafariBrowserExampleScreenState
extends State<ChromeSafariBrowserExampleScreen> { extends State<ChromeSafariBrowserExampleScreen> {
@override @override
void initState() { void initState() {
widget.browser.addMenuItem(new ChromeSafariBrowserMenuItem(id: 1, label: 'Custom item menu 1', action: (url, title) {
print('Custom item menu 1 clicked!');
print(url);
print(title);
}));
widget.browser.addMenuItem(new ChromeSafariBrowserMenuItem(id: 2, label: 'Custom item menu 2', action: (url, title) {
print('Custom item menu 2 clicked!');
print(url);
print(title);
}));
super.initState(); super.initState();
} }
...@@ -53,7 +63,10 @@ class _ChromeSafariBrowserExampleScreenState ...@@ -53,7 +63,10 @@ class _ChromeSafariBrowserExampleScreenState
url: "https://flutter.dev/", url: "https://flutter.dev/",
options: ChromeSafariBrowserClassOptions( options: ChromeSafariBrowserClassOptions(
android: AndroidChromeCustomTabsOptions(addDefaultShareMenuItem: false, keepAliveEnabled: true), android: AndroidChromeCustomTabsOptions(addDefaultShareMenuItem: false, keepAliveEnabled: true),
ios: IOSSafariOptions(barCollapsingEnabled: true))); ios: IOSSafariOptions(
dismissButtonStyle: IOSSafariDismissButtonStyle.CLOSE,
presentationStyle: IOSUIModalPresentationStyle.OVER_FULL_SCREEN
)));
}, },
child: Text("Open Chrome Safari Browser")), child: Text("Open Chrome Safari Browser")),
)); ));
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'main.dart';
class HeadlessInAppWebViewExampleScreen extends StatefulWidget {
@override
_HeadlessInAppWebViewExampleScreenState createState() =>
new _HeadlessInAppWebViewExampleScreenState();
}
class _HeadlessInAppWebViewExampleScreenState extends State<HeadlessInAppWebViewExampleScreen> {
HeadlessInAppWebView headlessWebView;
String url = "";
@override
void initState() {
super.initState();
headlessWebView = new HeadlessInAppWebView(
initialUrl: "https://flutter.dev/",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
),
),
onWebViewCreated: (controller) {
print('HeadlessInAppWebView created!');
},
onConsoleMessage: (controller, consoleMessage) {
print("CONSOLE MESSAGE: " + consoleMessage.message);
},
onLoadStart: (controller, url) async {
print("onLoadStart $url");
setState(() {
this.url = url;
});
},
onLoadStop: (controller, url) async {
print("onLoadStop $url");
setState(() {
this.url = url;
});
},
onUpdateVisitedHistory: (InAppWebViewController controller, String url, bool androidIsReload) {
print("onUpdateVisitedHistory $url");
setState(() {
this.url = url;
});
},
);
}
@override
void dispose() {
super.dispose();
headlessWebView.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"HeadlessInAppWebView",
)),
drawer: myDrawer(context: context),
body: SafeArea(
child: Column(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
),
Center(
child: RaisedButton(
onPressed: () async {
await headlessWebView.dispose();
await headlessWebView.run();
},
child: Text("Run HeadlessInAppWebView")),
),
Center(
child: RaisedButton(
onPressed: () async {
try {
await headlessWebView.webViewController.evaluateJavascript(source: """console.log('Here is the message!');""");
} on MissingPluginException catch(e) {
print("HeadlessInAppWebView is not running. Click on \"Run HeadlessInAppWebView\"!");
}
},
child: Text("Send console.log message")),
),
Center(
child: RaisedButton(
onPressed: () {
headlessWebView.dispose();
},
child: Text("Dispose HeadlessInAppWebView")),
)
])
)
);
}
}
...@@ -90,7 +90,7 @@ class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> { ...@@ -90,7 +90,7 @@ class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
await widget.browser.openFile( await widget.browser.openFile(
assetFilePath: "assets/index.html", assetFilePath: "assets/index.html",
options: InAppBrowserClassOptions( options: InAppBrowserClassOptions(
inAppWebViewWidgetOptions: InAppWebViewWidgetOptions( inAppWebViewGroupOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
debuggingEnabled: true, debuggingEnabled: true,
useShouldOverrideUrlLoading: true, useShouldOverrideUrlLoading: true,
......
...@@ -52,7 +52,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> { ...@@ -52,7 +52,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
debuggingEnabled: true, debuggingEnabled: true,
), ),
......
...@@ -2,8 +2,8 @@ import 'dart:async'; ...@@ -2,8 +2,8 @@ import 'dart:async';
import 'package:flutter/material.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/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_webiew_example.screen.dart';
import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart'; import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart';
...@@ -44,6 +44,12 @@ Drawer myDrawer({@required BuildContext context}) { ...@@ -44,6 +44,12 @@ Drawer myDrawer({@required BuildContext context}) {
Navigator.pushReplacementNamed(context, '/'); Navigator.pushReplacementNamed(context, '/');
}, },
), ),
ListTile(
title: Text('HeadlessInAppWebView'),
onTap: () {
Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView');
},
),
], ],
), ),
); );
...@@ -74,6 +80,7 @@ class _MyAppState extends State<MyApp> { ...@@ -74,6 +80,7 @@ class _MyAppState extends State<MyApp> {
'/': (context) => InAppWebViewExampleScreen(), '/': (context) => InAppWebViewExampleScreen(),
'/InAppBrowser': (context) => InAppBrowserExampleScreen(), '/InAppBrowser': (context) => InAppBrowserExampleScreen(),
'/ChromeSafariBrowser': (context) => ChromeSafariBrowserExampleScreen(), '/ChromeSafariBrowser': (context) => ChromeSafariBrowserExampleScreen(),
'/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(),
} }
); );
} }
......
...@@ -56,7 +56,7 @@ class InAppWebViewAjaxTestState extends WidgetTestState { ...@@ -56,7 +56,7 @@ class InAppWebViewAjaxTestState extends WidgetTestState {
</html> </html>
"""), """),
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -29,7 +29,7 @@ class InAppWebViewContentBlockerTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewContentBlockerTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -30,7 +30,7 @@ class InAppWebViewCookieManagerTestState extends WidgetTestState { ...@@ -30,7 +30,7 @@ class InAppWebViewCookieManagerTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -74,7 +74,7 @@ class InAppWebViewFetchTestState extends WidgetTestState { ...@@ -74,7 +74,7 @@ class InAppWebViewFetchTestState extends WidgetTestState {
</html> </html>
"""), """),
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -37,7 +37,7 @@ class InAppWebViewHttpAuthCredentialDatabaseTestState extends WidgetTestState { ...@@ -37,7 +37,7 @@ class InAppWebViewHttpAuthCredentialDatabaseTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/", initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -65,7 +65,7 @@ class InAppWebViewInitialDataTestState extends WidgetTestState { ...@@ -65,7 +65,7 @@ class InAppWebViewInitialDataTestState extends WidgetTestState {
</html> </html>
"""), """),
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -28,7 +28,7 @@ class InAppWebViewInitialFileTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewInitialFileTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_initial_file_test.html", initialFile: "test_assets/in_app_webview_initial_file_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -29,7 +29,7 @@ class InAppWebViewInitialUrlTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewInitialUrlTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -43,7 +43,7 @@ class InAppWebViewJavaScriptHandlerTestState extends WidgetTestState { ...@@ -43,7 +43,7 @@ class InAppWebViewJavaScriptHandlerTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_javascript_handler_test.html", initialFile: "test_assets/in_app_webview_javascript_handler_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -28,7 +28,7 @@ class InAppWebViewOnConsoleMessageTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewOnConsoleMessageTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_console_message_test.html", initialFile: "test_assets/in_app_webview_on_console_message_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -28,7 +28,7 @@ class InAppWebViewOnCreateWindowTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewOnCreateWindowTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_create_window_test.html", initialFile: "test_assets/in_app_webview_on_create_window_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -48,7 +48,7 @@ class InAppWebViewOnDownloadStartTestState extends WidgetTestState { ...@@ -48,7 +48,7 @@ class InAppWebViewOnDownloadStartTestState extends WidgetTestState {
</html> </html>
"""), """),
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -28,7 +28,7 @@ class InAppWebViewOnFindResultReceivedTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewOnFindResultReceivedTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_initial_file_test.html", initialFile: "test_assets/in_app_webview_initial_file_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -36,7 +36,7 @@ class InAppWebViewOnJsDialogTestState extends WidgetTestState { ...@@ -36,7 +36,7 @@ class InAppWebViewOnJsDialogTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_js_dialog_test.html", initialFile: "test_assets/in_app_webview_on_js_dialog_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadErrorTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadErrorTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://not-existing-domain.org/", initialUrl: "https://not-existing-domain.org/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadHttpErrorTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadHttpErrorTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://google.com/404", initialUrl: "https://google.com/404",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadResourceCustomSchemeTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewOnLoadResourceCustomSchemeTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_load_resource_custom_scheme_test.html", initialFile: "test_assets/in_app_webview_on_load_resource_custom_scheme_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -34,7 +34,7 @@ class InAppWebViewOnLoadResourceTestState extends WidgetTestState { ...@@ -34,7 +34,7 @@ class InAppWebViewOnLoadResourceTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_load_resource_test.html", initialFile: "test_assets/in_app_webview_on_load_resource_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -28,7 +28,7 @@ class InAppWebViewOnNavigationStateChangeTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewOnNavigationStateChangeTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -29,7 +29,7 @@ class InAppWebViewOnProgressChangedTestState extends WidgetTestState { ...@@ -29,7 +29,7 @@ class InAppWebViewOnProgressChangedTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -30,7 +30,7 @@ class InAppWebViewOnReceivedHttpAuthRequestTestState extends WidgetTestState { ...@@ -30,7 +30,7 @@ class InAppWebViewOnReceivedHttpAuthRequestTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/", initialUrl: "http://${environment["NODE_SERVER_IP"]}:8081/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -31,7 +31,7 @@ class InAppWebViewOnSafeBrowsingHitTestState extends WidgetTestState { ...@@ -31,7 +31,7 @@ class InAppWebViewOnSafeBrowsingHitTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: (Platform.isAndroid) ? "chrome://safe-browsing/match?type=malware" : "https://flutter.dev/", initialUrl: (Platform.isAndroid) ? "chrome://safe-browsing/match?type=malware" : "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
// if I set javaScriptEnabled to true, it will crash! // if I set javaScriptEnabled to true, it will crash!
javaScriptEnabled: false, javaScriptEnabled: false,
......
...@@ -30,7 +30,7 @@ class InAppWebViewOnScrollChangedTestState extends WidgetTestState { ...@@ -30,7 +30,7 @@ class InAppWebViewOnScrollChangedTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://flutter.dev/", initialUrl: "https://flutter.dev/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -28,7 +28,7 @@ class InAppWebViewShouldOverrideUrlLoadingTestState extends WidgetTestState { ...@@ -28,7 +28,7 @@ class InAppWebViewShouldOverrideUrlLoadingTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialFile: "test_assets/in_app_webview_initial_file_test.html", initialFile: "test_assets/in_app_webview_initial_file_test.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true, debuggingEnabled: true,
......
...@@ -30,7 +30,7 @@ class InAppWebViewSslRequestTestState extends WidgetTestState { ...@@ -30,7 +30,7 @@ class InAppWebViewSslRequestTestState extends WidgetTestState {
child: InAppWebView( child: InAppWebView(
initialUrl: "https://${environment["NODE_SERVER_IP"]}:4433/", initialUrl: "https://${environment["NODE_SERVER_IP"]}:4433/",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
debuggingEnabled: true debuggingEnabled: true
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/build" /> <excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/ios/Flutter/App.framework/flutter_assets/packages" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/App.framework/flutter_assets/packages" /> <excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/App.framework/flutter_assets/packages" />
<excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.pub" /> <excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.pub" />
......
...@@ -41,7 +41,8 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -41,7 +41,8 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
let uuidFallback: String = arguments!["uuidFallback"] as! String let uuidFallback: String = arguments!["uuidFallback"] as! String
let headersFallback = arguments!["headersFallback"] as! [String: String] let headersFallback = arguments!["headersFallback"] as! [String: String]
let optionsFallback = arguments!["optionsFallback"] as! [String: Any?] let optionsFallback = arguments!["optionsFallback"] as! [String: Any?]
open(uuid: uuid, url: url, options: options, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, result: result) let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
open(uuid: uuid, url: url, options: options, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, menuItemList: menuItemList, result: result)
break break
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
...@@ -49,7 +50,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -49,7 +50,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
} }
} }
public func open(uuid: String, url: String, options: [String: Any?], uuidFallback: String?, headersFallback: [String: String], optionsFallback: [String: Any?], result: @escaping FlutterResult) { public func open(uuid: String, url: String, options: [String: Any?], uuidFallback: String?, headersFallback: [String: String], optionsFallback: [String: Any?], menuItemList: [[String: Any]], result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL let absoluteUrl = URL(string: url)!.absoluteURL
if self.previousStatusBarStyle == -1 { if self.previousStatusBarStyle == -1 {
...@@ -85,10 +86,12 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -85,10 +86,12 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
} }
safari.uuid = uuid safari.uuid = uuid
safari.menuItemList = menuItemList
safari.prepareMethodChannel() safari.prepareMethodChannel()
safari.delegate = safari safari.delegate = safari
safari.tmpWindow = tmpWindow safari.tmpWindow = tmpWindow
safari.safariOptions = safariOptions safari.safariOptions = safariOptions
safari.prepareSafariBrowser()
tmpController.present(safari, animated: true) { tmpController.present(safari, animated: true) {
result(true) result(true)
......
...@@ -12,22 +12,27 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor ...@@ -12,22 +12,27 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
private weak var registrar: FlutterPluginRegistrar? private weak var registrar: FlutterPluginRegistrar?
var webView: InAppWebView? var webView: InAppWebView?
var viewId: Int64 = 0 var viewId: Any = 0
var channel: FlutterMethodChannel? var channel: FlutterMethodChannel?
var myView: UIView? var myView: UIView?
init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: NSDictionary) { init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, arguments args: NSDictionary) {
super.init() super.init()
self.registrar = registrar self.registrar = registrar
self.viewId = viewId self.viewId = viewId
myView = UIView(frame: frame) var channelName = ""
if let id = viewId as? Int64 {
let channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(viewId) channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(id)
} else if let id = viewId as? String {
channelName = "com.pichillilorenzo/flutter_inappwebview_" + id
}
channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger())
channel!.setMethodCallHandler(LeakAvoider(delegate: self).handle) channel!.setMethodCallHandler(LeakAvoider(delegate: self).handle)
myView = UIView(frame: frame)
let initialUrl = args["initialUrl"] as? String let initialUrl = args["initialUrl"] as? String
let initialFile = args["initialFile"] as? String let initialFile = args["initialFile"] as? String
let initialData = args["initialData"] as? [String: String] let initialData = args["initialData"] as? [String: String]
...@@ -74,6 +79,20 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor ...@@ -74,6 +79,20 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
} }
} }
load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders)
if (frame.isEmpty && viewId is String) {
/// Note: The WKWebView behaves very unreliable when rendering offscreen
/// on a device. This is especially true with JavaScript, which simply
/// won't be executed sometimes.
/// Therefore, I decided to add this very ugly hack where the rendering
/// webview will be added to the view hierarchy (between the
/// rootViewController's view and the key window).
self.myView!.alpha = 0.01
UIApplication.shared.keyWindow!.insertSubview(self.myView!, at: 0)
let arguments: [String: Any] = ["uuid": viewId]
channel!.invokeMethod("onHeadlessWebViewCreated", arguments: arguments)
}
} }
deinit { deinit {
......
//
// HeadlessInAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/2020.
//
import Foundation
import Flutter
import UIKit
import WebKit
import Foundation
import AVFoundation
public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin {
static var registrar: FlutterPluginRegistrar?
static var channel: FlutterMethodChannel?
var flutterWebViews: [String: FlutterWebViewController] = [:]
public static func register(with registrar: FlutterPluginRegistrar) {
}
init(registrar: FlutterPluginRegistrar) {
super.init()
HeadlessInAppWebViewManager.registrar = registrar
HeadlessInAppWebViewManager.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_headless_inappwebview", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(self, channel: HeadlessInAppWebViewManager.channel!)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
let uuid: String = arguments!["uuid"] as! String
switch call.method {
case "createHeadlessWebView":
let params = arguments!["params"] as! [String: Any?]
createHeadlessWebView(uuid: uuid, params: params)
result(true)
break
case "disposeHeadlessWebView":
disposeHeadlessWebView(uuid: uuid)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func createHeadlessWebView(uuid: String, params: [String: Any?]) {
let controller = FlutterWebViewController(registrar: HeadlessInAppWebViewManager.registrar!,
withFrame: CGRect.zero,
viewIdentifier: uuid,
arguments: params as NSDictionary)
flutterWebViews[uuid] = controller
}
public func disposeHeadlessWebView(uuid: String) {
if let _ = flutterWebViews[uuid] {
flutterWebViews.removeValue(forKey: uuid)
}
}
}
...@@ -15,6 +15,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -15,6 +15,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
var tmpWindow: UIWindow? var tmpWindow: UIWindow?
var safariOptions: SafariBrowserOptions? var safariOptions: SafariBrowserOptions?
var uuid: String = "" var uuid: String = ""
var menuItemList: [[String: Any]] = []
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
...@@ -30,11 +31,20 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -30,11 +31,20 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "close":
close()
break
default:
result(FlutterMethodNotImplemented)
break
}
} }
public override func viewWillAppear(_ animated: Bool) { public override func viewWillAppear(_ animated: Bool) {
prepareSafariBrowser() // prepareSafariBrowser()
super.viewWillAppear(animated) super.viewWillAppear(animated)
onChromeSafariBrowserOpened() onChromeSafariBrowserOpened()
} }
...@@ -83,23 +93,25 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -83,23 +93,25 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
} }
public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] { public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
// print("activityItemsFor") var uiActivities: [UIActivity] = []
// print(URL) menuItemList.forEach { (menuItem) in
// print(title) let activity = CustomUIActivity(uuid: uuid, id: menuItem["id"] as! Int64, url: URL, title: title, label: menuItem["label"] as? String, type: nil, image: nil)
return [] uiActivities.append(activity)
}
return uiActivities
} }
//
public func safariViewController(_ controller: SFSafariViewController, excludedActivityTypesFor URL: URL, title: String?) -> [UIActivity.ActivityType] { // public func safariViewController(_ controller: SFSafariViewController, excludedActivityTypesFor URL: URL, title: String?) -> [UIActivity.ActivityType] {
// print("excludedActivityTypesFor") // print("excludedActivityTypesFor")
// print(URL) // print(URL)
// print(title) // print(title)
return [] // return []
} // }
//
public func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo URL: URL) { // public func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo URL: URL) {
// print("initialLoadDidRedirectTo") // print("initialLoadDidRedirectTo")
// print(URL) // print(URL)
} // }
public func onChromeSafariBrowserOpened() { public func onChromeSafariBrowserOpened() {
channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: []) channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: [])
...@@ -146,3 +158,54 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -146,3 +158,54 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
return hexInt return hexInt
} }
} }
class CustomUIActivity : UIActivity {
var uuid: String
var id: Int64
var url: URL
var title: String?
var type: UIActivity.ActivityType?
var label: String?
var image: UIImage?
init(uuid: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) {
self.uuid = uuid
self.id = id
self.url = url
self.title = title
self.label = label
self.type = type
self.image = image
}
override class var activityCategory: UIActivity.Category {
return .action
}
override var activityType: UIActivity.ActivityType? {
return type
}
override var activityTitle: String? {
return label
}
override var activityImage: UIImage? {
return image
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override func perform() {
let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_chromesafaribrowser_" + uuid, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
let arguments: [String: Any?] = [
"url": url.absoluteString,
"title": title,
"id": id,
]
channel.invokeMethod("onChromeSafariBrowserMenuItemActionPerform", arguments: arguments)
}
}
...@@ -31,6 +31,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -31,6 +31,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var myWebStorageManager: Any? var myWebStorageManager: Any?
var credentialDatabase: CredentialDatabase? var credentialDatabase: CredentialDatabase?
var inAppBrowserManager: InAppBrowserManager? var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var chromeSafariBrowserManager: ChromeSafariBrowserManager? var chromeSafariBrowserManager: ChromeSafariBrowserManager?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
...@@ -46,6 +47,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -46,6 +47,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: "com.pichillilorenzo/flutter_inappwebview") registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: "com.pichillilorenzo/flutter_inappwebview")
inAppBrowserManager = InAppBrowserManager(registrar: registrar) inAppBrowserManager = InAppBrowserManager(registrar: registrar)
headlessInAppWebViewManager = HeadlessInAppWebViewManager(registrar: registrar)
chromeSafariBrowserManager = ChromeSafariBrowserManager(registrar: registrar) chromeSafariBrowserManager = ChromeSafariBrowserManager(registrar: registrar)
inAppWebViewStatic = InAppWebViewStatic(registrar: registrar) inAppWebViewStatic = InAppWebViewStatic(registrar: registrar)
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
library flutter_inappwebview; library flutter_inappwebview;
export 'src/types.dart'; export 'src/types.dart';
export 'src/webview.dart';
export 'src/in_app_webview_controller.dart';
export 'src/headless_in_app_webview.dart';
export 'src/in_app_webview.dart'; export 'src/in_app_webview.dart';
export 'src/in_app_browser.dart'; export 'src/in_app_browser.dart';
export 'src/cookie_manager.dart'; export 'src/cookie_manager.dart';
......
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:collection';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -7,8 +7,6 @@ import 'package:flutter/services.dart'; ...@@ -7,8 +7,6 @@ import 'package:flutter/services.dart';
import 'types.dart'; import 'types.dart';
import 'in_app_browser.dart'; import 'in_app_browser.dart';
///ChromeSafariBrowser class.
///
///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android ///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android
///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS. ///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
/// ///
...@@ -16,6 +14,7 @@ import 'in_app_browser.dart'; ...@@ -16,6 +14,7 @@ import 'in_app_browser.dart';
class ChromeSafariBrowser { class ChromeSafariBrowser {
String uuid; String uuid;
InAppBrowser browserFallback; InAppBrowser browserFallback;
Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap();
bool _isOpened = false; bool _isOpened = false;
MethodChannel _channel; MethodChannel _channel;
static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser'); static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser');
...@@ -42,6 +41,12 @@ class ChromeSafariBrowser { ...@@ -42,6 +41,12 @@ class ChromeSafariBrowser {
onClosed(); onClosed();
this._isOpened = false; this._isOpened = false;
break; break;
case "onChromeSafariBrowserMenuItemActionPerform":
String url = call.arguments["url"];
String title = call.arguments["title"];
int id = call.arguments["id"].toInt();
this._menuItems[id].action(url, title);
break;
default: default:
throw UnimplementedError("Unimplemented ${call.method} method"); throw UnimplementedError("Unimplemented ${call.method} method");
} }
...@@ -64,49 +69,45 @@ class ChromeSafariBrowser { ...@@ -64,49 +69,45 @@ class ChromeSafariBrowser {
assert(url != null && url.isNotEmpty); assert(url != null && url.isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $url!'); this.throwIsAlreadyOpened(message: 'Cannot open $url!');
Map<String, dynamic> optionsMap = {}; List<Map<String, dynamic>> menuItemList = new List();
if (Platform.isAndroid) _menuItems.forEach((key, value) {
optionsMap.addAll(options.android?.toMap() ?? {}); menuItemList.add({
else if (Platform.isIOS) "id": value.id,
optionsMap.addAll(options.ios?.toMap() ?? {}); "label": value.label
});
Map<String, dynamic> optionsFallbackMap = {}; });
if (optionsFallback != null) {
optionsFallbackMap
.addAll(optionsFallback.crossPlatform?.toMap() ?? {});
optionsFallbackMap.addAll(optionsFallback
.inAppWebViewWidgetOptions?.crossPlatform
?.toMap() ??
{});
if (Platform.isAndroid) {
optionsFallbackMap
.addAll(optionsFallback.android?.toMap() ?? {});
optionsFallbackMap.addAll(optionsFallback
.inAppWebViewWidgetOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
optionsFallbackMap
.addAll(optionsFallback.ios?.toMap() ?? {});
optionsFallbackMap.addAll(optionsFallback
.inAppWebViewWidgetOptions?.ios
?.toMap() ??
{});
}
}
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('options', () => optionsMap); args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('uuidFallback', args.putIfAbsent('uuidFallback',
() => (browserFallback != null) ? browserFallback.uuid : ''); () => (browserFallback != null) ? browserFallback.uuid : '');
args.putIfAbsent('headersFallback', () => headersFallback); args.putIfAbsent('headersFallback', () => headersFallback);
args.putIfAbsent('optionsFallback', () => optionsFallbackMap); args.putIfAbsent('optionsFallback', () => optionsFallback?.toMap() ?? {});
args.putIfAbsent('menuItemList', () => menuItemList);
await _sharedChannel.invokeMethod('open', args); await _sharedChannel.invokeMethod('open', args);
this._isOpened = true; this._isOpened = true;
} }
///Closes the [ChromeSafariBrowser] instance.
Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod("close", args);
}
///Adds a [ChromeSafariBrowserMenuItem] to the menu.
void addMenuItem(ChromeSafariBrowserMenuItem menuItem) {
this._menuItems[menuItem.id] = menuItem;
}
///Adds a list of [ChromeSafariBrowserMenuItem] to the menu.
void addMenuItems(List<ChromeSafariBrowserMenuItem> menuItems) {
menuItems.forEach((menuItem) {
this._menuItems[menuItem.id] = menuItem;
});
}
///Event fires when the [ChromeSafariBrowser] is opened. ///Event fires when the [ChromeSafariBrowser] is opened.
void onOpened() {} void onOpened() {}
...@@ -137,3 +138,11 @@ class ChromeSafariBrowser { ...@@ -137,3 +138,11 @@ class ChromeSafariBrowser {
} }
} }
} }
class ChromeSafariBrowserMenuItem {
int id;
String label;
final void Function(String url, String title) action;
ChromeSafariBrowserMenuItem({@required this.id, @required this.label, @required this.action});
}
\ No newline at end of file
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'types.dart'; import 'types.dart';
///ContentBlocker class represents a set of rules to use block content in the browser window. ///Class that represents a set of rules to use block content in the browser window.
/// ///
///On iOS, it uses [WKContentRuleListStore](https://developer.apple.com/documentation/webkit/wkcontentruleliststore). ///On iOS, it uses [WKContentRuleListStore](https://developer.apple.com/documentation/webkit/wkcontentruleliststore).
///On Android, it uses a custom implementation because such functionality doesn't exist. ///On Android, it uses a custom implementation because such functionality doesn't exist.
......
...@@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; ...@@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
import 'types.dart'; import 'types.dart';
///CookieManager class implements a singleton object (shared instance) which manages the cookies used by WebView instances. ///Class that implements a singleton object (shared instance) which manages the cookies used by WebView instances.
/// ///
///**NOTE for iOS**: available from iOS 11.0+. ///**NOTE for iOS**: available from iOS 11.0+.
class CookieManager { class CookieManager {
......
import 'package:flutter/services.dart';
import 'types.dart';
import 'webview.dart';
import 'in_app_webview_controller.dart';
///Class that represents a WebView in headless mode.
///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
///
///Remember to dispose it when you don't need it anymore.
class HeadlessInAppWebView implements WebView {
String uuid;
bool _isDisposed = true;
static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview');
///WebView Controller that can be used to access the [InAppWebViewController] API.
InAppWebViewController webViewController;
HeadlessInAppWebView(
{this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions}) {
uuid = uuidGenerator.v4();
webViewController = new InAppWebViewController(uuid, this);
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onHeadlessWebViewCreated":
onWebViewCreated(webViewController);
break;
default:
return webViewController.handleMethod(call);
}
}
///Runs the headless WebView.
Future<void> run() async {
if (!_isDisposed) {
return;
}
_isDisposed = false;
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('params', () => <String, dynamic>{
'initialUrl': '${Uri.parse(this.initialUrl)}',
'initialFile': this.initialFile,
'initialData': this.initialData?.toMap(),
'initialHeaders': this.initialHeaders,
'initialOptions': this.initialOptions?.toMap()
});
await _sharedChannel.invokeMethod('createHeadlessWebView', args);
}
///Disposes the headless WebView.
Future<void> dispose() async {
if (_isDisposed) {
return;
}
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid);
await _sharedChannel.invokeMethod('disposeHeadlessWebView', args);
_isDisposed = true;
}
@override
final Future<void> Function(InAppWebViewController controller)
androidOnGeolocationPermissionsHidePrompt;
@override
final Future<GeolocationPermissionShowPromptResponse> Function(
InAppWebViewController controller, String origin)
androidOnGeolocationPermissionsShowPrompt;
@override
final Future<PermissionRequestResponse> Function(
InAppWebViewController controller,
String origin,
List<String> resources) androidOnPermissionRequest;
@override
final Future<SafeBrowsingResponse> Function(InAppWebViewController controller,
String url, SafeBrowsingThreat threatType) androidOnSafeBrowsingHit;
@override
final InAppWebViewInitialData initialData;
@override
final String initialFile;
@override
final Map<String, String> initialHeaders;
@override
final InAppWebViewGroupOptions initialOptions;
@override
final String initialUrl;
@override
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
@override
final Future<void> Function(InAppWebViewController controller)
iosOnDidReceiveServerRedirectForProvisionalNavigation;
@override
final Future<void> Function(InAppWebViewController controller)
iosOnWebContentProcessDidTerminate;
@override
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxProgress;
@override
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxReadyStateChange;
@override
final void Function(
InAppWebViewController controller, ConsoleMessage consoleMessage)
onConsoleMessage;
@override
final void Function(InAppWebViewController controller,
OnCreateWindowRequest onCreateWindowRequest) onCreateWindow;
@override
final void Function(InAppWebViewController controller, String url)
onDownloadStart;
@override
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) onFindResultReceived;
@override
final Future<JsAlertResponse> Function(
InAppWebViewController controller, String message) onJsAlert;
@override
final Future<JsConfirmResponse> Function(
InAppWebViewController controller, String message) onJsConfirm;
@override
final Future<JsPromptResponse> Function(InAppWebViewController controller,
String message, String defaultValue) onJsPrompt;
@override
final void Function(InAppWebViewController controller, String url, int code,
String message) onLoadError;
@override
final void Function(InAppWebViewController controller, String url,
int statusCode, String description) onLoadHttpError;
@override
final void Function(
InAppWebViewController controller, LoadedResource resource)
onLoadResource;
@override
final Future<CustomSchemeResponse> Function(
InAppWebViewController controller, String scheme, String url)
onLoadResourceCustomScheme;
@override
final void Function(InAppWebViewController controller, String url)
onLoadStart;
@override
final void Function(InAppWebViewController controller, String url) onLoadStop;
@override
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
@override
final void Function(InAppWebViewController controller, String url) onPrint;
@override
final void Function(InAppWebViewController controller, int progress)
onProgressChanged;
@override
final Future<ClientCertResponse> Function(
InAppWebViewController controller, ClientCertChallenge challenge)
onReceivedClientCertRequest;
@override
final Future<HttpAuthResponse> Function(
InAppWebViewController controller, HttpAuthChallenge challenge)
onReceivedHttpAuthRequest;
@override
final Future<ServerTrustAuthResponse> Function(
InAppWebViewController controller, ServerTrustChallenge challenge)
onReceivedServerTrustAuthRequest;
@override
final void Function(InAppWebViewController controller, int x, int y)
onScrollChanged;
@override
final void Function(
InAppWebViewController controller, String url, bool androidIsReload)
onUpdateVisitedHistory;
@override
final void Function(InAppWebViewController controller) onWebViewCreated;
@override
final Future<AjaxRequest> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
shouldInterceptAjaxRequest;
@override
final Future<FetchRequest> Function(
InAppWebViewController controller, FetchRequest fetchRequest)
shouldInterceptFetchRequest;
@override
final Future<ShouldOverrideUrlLoadingAction> Function(
InAppWebViewController controller,
ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest)
shouldOverrideUrlLoading;
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; ...@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
import 'types.dart'; import 'types.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
///HttpAuthCredentialDatabase class implements a singleton object (shared instance) which manages the shared HTTP auth credentials cache. ///Class that implements a singleton object (shared instance) which manages the shared HTTP auth credentials cache.
///On iOS, this class uses the [URLCredentialStorage](https://developer.apple.com/documentation/foundation/urlcredentialstorage) class. ///On iOS, this class uses the [URLCredentialStorage](https://developer.apple.com/documentation/foundation/urlcredentialstorage) class.
///On Android, this class has a custom implementation using `android.database.sqlite.SQLiteDatabase` because [WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase) ///On Android, this class has a custom implementation using `android.database.sqlite.SQLiteDatabase` because [WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase)
///doesn't offer the same functionalities as iOS `URLCredentialStorage`. ///doesn't offer the same functionalities as iOS `URLCredentialStorage`.
......
...@@ -4,14 +4,13 @@ import 'dart:io'; ...@@ -4,14 +4,13 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'in_app_webview_controller.dart';
import 'webview_options.dart'; import 'webview_options.dart';
import 'types.dart'; import 'types.dart';
import 'in_app_webview.dart' show InAppWebViewController;
///InAppBrowser class. [webViewController] can be used to access the [InAppWebView] API.
///
///This class uses the native WebView of the platform. ///This class uses the native WebView of the platform.
///The [webViewController] field can be used to access the [InAppWebViewController] API.
class InAppBrowser { class InAppBrowser {
String uuid; String uuid;
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap = Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
...@@ -20,7 +19,7 @@ class InAppBrowser { ...@@ -20,7 +19,7 @@ class InAppBrowser {
MethodChannel _channel; MethodChannel _channel;
static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser');
/// WebView Controller that can be used to access the [InAppWebView] API. /// WebView Controller that can be used to access the [InAppWebViewController] API.
InAppWebViewController webViewController; InAppWebViewController webViewController;
/// ///
...@@ -63,29 +62,11 @@ class InAppBrowser { ...@@ -63,29 +62,11 @@ class InAppBrowser {
assert(url != null && url.isNotEmpty); assert(url != null && url.isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $url!'); this.throwIsAlreadyOpened(message: 'Cannot open $url!');
Map<String, dynamic> optionsMap = {};
optionsMap.addAll(options.crossPlatform?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid) {
optionsMap.addAll(options.android?.toMap() ?? {});
optionsMap.addAll(options
.inAppWebViewWidgetOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
optionsMap.addAll(options.ios?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.ios?.toMap() ??
{});
}
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => optionsMap); args.putIfAbsent('options', () => options?.toMap() ?? {});
await _sharedChannel.invokeMethod('openUrl', args); await _sharedChannel.invokeMethod('openUrl', args);
} }
...@@ -129,29 +110,13 @@ class InAppBrowser { ...@@ -129,29 +110,13 @@ class InAppBrowser {
assert(assetFilePath != null && assetFilePath.isNotEmpty); assert(assetFilePath != null && assetFilePath.isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $assetFilePath!'); this.throwIsAlreadyOpened(message: 'Cannot open $assetFilePath!');
Map<String, dynamic> optionsMap = {};
optionsMap.addAll(options.crossPlatform?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid) {
optionsMap.addAll(options.android?.toMap() ?? {});
optionsMap.addAll(options
.inAppWebViewWidgetOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
optionsMap.addAll(options.ios?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.ios?.toMap() ??
{});
}
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => assetFilePath); args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => optionsMap); args.putIfAbsent('options', () => options?.toMap() ?? {});
await _sharedChannel.invokeMethod('openFile', args); await _sharedChannel.invokeMethod('openFile', args);
} }
...@@ -173,27 +138,9 @@ class InAppBrowser { ...@@ -173,27 +138,9 @@ class InAppBrowser {
InAppBrowserClassOptions options}) async { InAppBrowserClassOptions options}) async {
assert(data != null); assert(data != null);
Map<String, dynamic> optionsMap = {};
optionsMap.addAll(options.crossPlatform?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid) {
optionsMap.addAll(options.android?.toMap() ?? {});
optionsMap.addAll(options
.inAppWebViewWidgetOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
optionsMap.addAll(options.ios?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.ios?.toMap() ??
{});
}
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('options', () => optionsMap); args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('data', () => data); args.putIfAbsent('data', () => data);
args.putIfAbsent('mimeType', () => mimeType); args.putIfAbsent('mimeType', () => mimeType);
args.putIfAbsent('encoding', () => encoding); args.putIfAbsent('encoding', () => encoding);
...@@ -243,26 +190,8 @@ class InAppBrowser { ...@@ -243,26 +190,8 @@ class InAppBrowser {
Future<void> setOptions({@required InAppBrowserClassOptions options}) async { Future<void> setOptions({@required InAppBrowserClassOptions options}) async {
this.throwIsNotOpened(); this.throwIsNotOpened();
Map<String, dynamic> optionsMap = {};
optionsMap.addAll(options.crossPlatform?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid) {
optionsMap.addAll(options.android?.toMap() ?? {});
optionsMap.addAll(options
.inAppWebViewWidgetOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
optionsMap.addAll(options.ios?.toMap() ?? {});
optionsMap.addAll(
options.inAppWebViewWidgetOptions?.ios?.toMap() ??
{});
}
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('options', () => optionsMap); args.putIfAbsent('options', () => options?.toMap() ?? {});
await _channel.invokeMethod('setOptions', args); await _channel.invokeMethod('setOptions', args);
} }
...@@ -279,19 +208,19 @@ class InAppBrowser { ...@@ -279,19 +208,19 @@ class InAppBrowser {
options = options.cast<String, dynamic>(); options = options.cast<String, dynamic>();
inAppBrowserClassOptions.crossPlatform = inAppBrowserClassOptions.crossPlatform =
InAppBrowserOptions.fromMap(options); InAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions.inAppWebViewWidgetOptions = InAppWebViewWidgetOptions(); inAppBrowserClassOptions.inAppWebViewGroupOptions = InAppWebViewGroupOptions();
inAppBrowserClassOptions.inAppWebViewWidgetOptions.crossPlatform = inAppBrowserClassOptions.inAppWebViewGroupOptions.crossPlatform =
InAppWebViewOptions.fromMap(options); InAppWebViewOptions.fromMap(options);
if (Platform.isAndroid) { if (Platform.isAndroid) {
inAppBrowserClassOptions.android = inAppBrowserClassOptions.android =
AndroidInAppBrowserOptions.fromMap(options); AndroidInAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions inAppBrowserClassOptions
.inAppWebViewWidgetOptions.android = .inAppWebViewGroupOptions.android =
AndroidInAppWebViewOptions.fromMap(options); AndroidInAppWebViewOptions.fromMap(options);
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
inAppBrowserClassOptions.ios = inAppBrowserClassOptions.ios =
IOSInAppBrowserOptions.fromMap(options); IOSInAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions.inAppWebViewWidgetOptions inAppBrowserClassOptions.inAppWebViewGroupOptions
.ios = IOSInAppWebViewOptions.fromMap(options); .ios = IOSInAppWebViewOptions.fromMap(options);
} }
} }
......
...@@ -4,8 +4,6 @@ import 'dart:async'; ...@@ -4,8 +4,6 @@ import 'dart:async';
import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/services.dart' show rootBundle;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
///InAppLocalhostServer class.
///
///This class allows you to create a simple server on `http://localhost:[port]/` in order to be able to load your assets file on a server. The default [port] value is `8080`. ///This class allows you to create a simple server on `http://localhost:[port]/` in order to be able to load your assets file on a server. The default [port] value is `8080`.
class InAppLocalhostServer { class InAppLocalhostServer {
HttpServer _server; HttpServer _server;
......
import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -10,11 +6,9 @@ import 'package:flutter/services.dart'; ...@@ -10,11 +6,9 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:html/parser.dart' show parse; import 'webview.dart';
import 'types.dart'; import 'types.dart';
import 'in_app_browser.dart'; import 'in_app_webview_controller.dart';
import 'webview_options.dart';
///List of forbidden names for JavaScript handlers. ///List of forbidden names for JavaScript handlers.
const javaScriptHandlerForbiddenNames = [ const javaScriptHandlerForbiddenNames = [
...@@ -23,311 +17,11 @@ const javaScriptHandlerForbiddenNames = [ ...@@ -23,311 +17,11 @@ const javaScriptHandlerForbiddenNames = [
"onAjaxReadyStateChange", "onAjaxReadyStateChange",
"onAjaxProgress", "onAjaxProgress",
"shouldInterceptFetchRequest", "shouldInterceptFetchRequest",
"onPrint", "onPrint"
"flutterInAppWebViewDropDownWorkaroud"
]; ];
///InAppWebView Widget class.
///
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree. ///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
class InAppWebView extends StatefulWidget { class InAppWebView extends StatefulWidget implements WebView {
///Event fired when the [InAppWebView] is created.
final void Function(InAppWebViewController controller) onWebViewCreated;
///Event fired when the [InAppWebView] starts to load an [url].
final void Function(InAppWebViewController controller, String url)
onLoadStart;
///Event fired when the [InAppWebView] finishes loading an [url].
final void Function(InAppWebViewController controller, String url) onLoadStop;
///Event fired when the [InAppWebView] encounters an error loading an [url].
final void Function(InAppWebViewController controller, String url, int code,
String message) onLoadError;
///Event fired when the [InAppWebView] main page receives an HTTP error.
///
///[url] represents the url of the main page that received the HTTP error.
///
///[statusCode] represents the status code of the response. HTTP errors have status codes >= 400.
///
///[description] represents the description of the HTTP error. On iOS, it is always an empty string.
///
///**NOTE**: available on Android 23+.
final void Function(InAppWebViewController controller, String url,
int statusCode, String description) onLoadHttpError;
///Event fired when the current [progress] of loading a page is changed.
final void Function(InAppWebViewController controller, int progress)
onProgressChanged;
///Event fired when the [InAppWebView] receives a [ConsoleMessage].
final void Function(
InAppWebViewController controller, ConsoleMessage consoleMessage)
onConsoleMessage;
///Give the host application a chance to take control when a URL is about to be loaded in the current WebView. This event is not called on the initial load of the WebView.
///
///Note that on Android 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 by default.
///However, if you want to cancel requests for subframes, you can use the [AndroidInAppWebViewOptions.regexToCancelSubFramesLoading] option
///to write a Regular Expression that, if the url request of a subframe matches, then the request of that subframe is canceled.
///
///Also, on Android, this method is not called for POST requests.
///
///[shouldOverrideUrlLoadingRequest] represents the navigation request.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldOverrideUrlLoading] option to `true`.
final Future<ShouldOverrideUrlLoadingAction> Function(InAppWebViewController controller, ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest)
shouldOverrideUrlLoading;
///Event fired when the [InAppWebView] loads a resource.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnLoadResource] and [InAppWebViewOptions.javaScriptEnabled] options to `true`.
final void Function(
InAppWebViewController controller, LoadedResource resource)
onLoadResource;
///Event fired when the [InAppWebView] scrolls.
///
///[x] represents the current horizontal scroll origin in pixels.
///
///[y] represents the current vertical scroll origin in pixels.
final void Function(InAppWebViewController controller, int x, int y)
onScrollChanged;
///Event fired when [InAppWebView] recognizes and starts a downloadable file.
///
///[url] represents the url of the file.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnDownloadStart] option to `true`.
final void Function(InAppWebViewController controller, String url)
onDownloadStart;
///Event fired when the [InAppWebView] finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`.
///
///[scheme] represents the scheme of the url.
///
///[url] represents the url of the request.
final Future<CustomSchemeResponse> Function(
InAppWebViewController controller, String scheme, String url)
onLoadResourceCustomScheme;
///Event fired when the [InAppWebView] requests the host application to create a new window,
///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side.
///
///[onCreateWindowRequest] represents the request.
///
///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`.
final void Function(InAppWebViewController controller, OnCreateWindowRequest onCreateWindowRequest)
onCreateWindow;
///Event fired when javascript calls the `alert()` method to display an alert dialog.
///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final Future<JsAlertResponse> Function(
InAppWebViewController controller, String message) onJsAlert;
///Event fired when javascript calls the `confirm()` method to display a confirm dialog.
///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final Future<JsConfirmResponse> Function(
InAppWebViewController controller, String message) onJsConfirm;
///Event fired when javascript calls the `prompt()` method to display a prompt dialog.
///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
///
///[defaultValue] represents the default value displayed in the prompt dialog.
final Future<JsPromptResponse> Function(InAppWebViewController controller,
String message, String defaultValue) onJsPrompt;
///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request.
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge].
final Future<HttpAuthResponse> Function(
InAppWebViewController controller, HttpAuthChallenge challenge)
onReceivedHttpAuthRequest;
///Event fired when the WebView need to perform server trust authentication (certificate validation).
///The host application must return either [ServerTrustAuthResponse] instance with [ServerTrustAuthResponseAction.CANCEL] or [ServerTrustAuthResponseAction.PROCEED].
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge].
final Future<ServerTrustAuthResponse> Function(
InAppWebViewController controller, ServerTrustChallenge challenge)
onReceivedServerTrustAuthRequest;
///Notify the host application to handle an SSL client certificate request.
///Webview stores the response in memory (for the life of the application) if [ClientCertResponseAction.PROCEED] or [ClientCertResponseAction.CANCEL]
///is called and does not call [onReceivedClientCertRequest] again for the same host and port pair.
///Note that, multiple layers in chromium network stack might be caching the responses.
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ClientCertChallenge].
final Future<ClientCertResponse> Function(
InAppWebViewController controller, ClientCertChallenge challenge)
onReceivedClientCertRequest;
///Event fired as find-on-page operations progress.
///The listener may be notified multiple times while the operation is underway, and the numberOfMatches value should not be considered final unless [isDoneCounting] is true.
///
///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
///
///[numberOfMatches] represents how many matches have been found.
///
///[isDoneCounting] whether the find operation has actually completed.
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) onFindResultReceived;
///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.
///
///[ajaxRequest] represents the `XMLHttpRequest`.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequest> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
shouldInterceptAjaxRequest;
///Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes.
///It gives the host application a chance to abort the request.
///
///[ajaxRequest] represents the [XMLHttpRequest].
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxReadyStateChange;
///Event fired as an `XMLHttpRequest` progress.
///It gives the host application a chance to abort the request.
///
///[ajaxRequest] represents the [XMLHttpRequest].
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxProgress;
///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.
///
///[fetchRequest] represents a resource request.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptFetchRequest] option 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
///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.
final Future<FetchRequest> Function(
InAppWebViewController controller, FetchRequest fetchRequest)
shouldInterceptFetchRequest;
///Event fired when the host application updates its visited links database.
///This event is also fired when the navigation state of the [InAppWebView] changes through the usage of
///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event
///or, also, when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url).
///
///[url] represents the url being visited.
///
///[androidIsReload] indicates if this url is being reloaded. Available only on Android.
final void Function(InAppWebViewController controller, String url, bool androidIsReload) onUpdateVisitedHistory;
///Event fired when `window.print()` is called from JavaScript side.
///
///[url] represents the url on which is called.
///
///**NOTE**: available on Android 21+.
final void Function(InAppWebViewController controller, String url) onPrint;
///Event fired when an HTML element of the webview has been clicked and held.
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(InAppWebViewController controller, LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
///
///[url] represents the url of the request.
///
///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat].
///
///**NOTE**: available only on Android 27+.
final Future<SafeBrowsingResponse> Function(InAppWebViewController controller,
String url, SafeBrowsingThreat threatType) androidOnSafeBrowsingHit;
///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied.
///
///[origin] represents the origin of the web page which is trying to access the restricted resources.
///
///[resources] represents the array of resources the web content wants to access.
///
///**NOTE**: available only on Android 23+.
final Future<PermissionRequestResponse> Function(
InAppWebViewController controller,
String origin,
List<String> resources) androidOnPermissionRequest;
///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin.
///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https.
///On non-secure origins geolocation requests are automatically denied.
///
///[origin] represents the origin of the web content attempting to use the Geolocation API.
///
///**NOTE**: available only on Android.
final Future<GeolocationPermissionShowPromptResponse> Function(
InAppWebViewController controller, String origin)
androidOnGeolocationPermissionsShowPrompt;
///Notify the host application that a request for Geolocation permissions, made with a previous call to [androidOnGeolocationPermissionsShowPrompt] has been canceled.
///Any related UI should therefore be hidden.
///
///**NOTE**: available only on Android.
final Future<void> Function(InAppWebViewController controller) androidOnGeolocationPermissionsHidePrompt;
///Invoked when the web view's web content process is terminated.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller) iosOnWebContentProcessDidTerminate;
///Called when the web view begins to receive web content.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
///Called when a web view receives a server redirect.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller) iosOnDidReceiveServerRedirectForProvisionalNavigation;
///Initial url that will be loaded.
final String initialUrl;
///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation.
final String initialFile;
///Initial [InAppWebViewInitialData] that will be loaded.
final InAppWebViewInitialData initialData;
///Initial headers that will be used.
final Map<String, String> initialHeaders;
///Initial options that will be used.
final InAppWebViewWidgetOptions initialOptions;
/// `gestureRecognizers` specifies which gestures should be consumed by the web view. /// `gestureRecognizers` specifies which gestures should be consumed by the web view.
/// It is possible for other gesture recognizers to be competing with the web view on pointer /// It is possible for other gesture recognizers to be competing with the web view on pointer
/// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
...@@ -383,6 +77,169 @@ class InAppWebView extends StatefulWidget { ...@@ -383,6 +77,169 @@ class InAppWebView extends StatefulWidget {
@override @override
_InAppWebViewState createState() => _InAppWebViewState(); _InAppWebViewState createState() => _InAppWebViewState();
@override
final Future<void> Function(InAppWebViewController controller)
androidOnGeolocationPermissionsHidePrompt;
@override
final Future<GeolocationPermissionShowPromptResponse> Function(
InAppWebViewController controller, String origin)
androidOnGeolocationPermissionsShowPrompt;
@override
final Future<PermissionRequestResponse> Function(
InAppWebViewController controller,
String origin,
List<String> resources) androidOnPermissionRequest;
@override
final Future<SafeBrowsingResponse> Function(InAppWebViewController controller,
String url, SafeBrowsingThreat threatType) androidOnSafeBrowsingHit;
@override
final InAppWebViewInitialData initialData;
@override
final String initialFile;
@override
final Map<String, String> initialHeaders;
@override
final InAppWebViewGroupOptions initialOptions;
@override
final String initialUrl;
@override
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
@override
final Future<void> Function(InAppWebViewController controller)
iosOnDidReceiveServerRedirectForProvisionalNavigation;
@override
final Future<void> Function(InAppWebViewController controller)
iosOnWebContentProcessDidTerminate;
@override
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxProgress;
@override
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxReadyStateChange;
@override
final void Function(
InAppWebViewController controller, ConsoleMessage consoleMessage)
onConsoleMessage;
@override
final void Function(InAppWebViewController controller,
OnCreateWindowRequest onCreateWindowRequest) onCreateWindow;
@override
final void Function(InAppWebViewController controller, String url)
onDownloadStart;
@override
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) onFindResultReceived;
@override
final Future<JsAlertResponse> Function(
InAppWebViewController controller, String message) onJsAlert;
@override
final Future<JsConfirmResponse> Function(
InAppWebViewController controller, String message) onJsConfirm;
@override
final Future<JsPromptResponse> Function(InAppWebViewController controller,
String message, String defaultValue) onJsPrompt;
@override
final void Function(InAppWebViewController controller, String url, int code,
String message) onLoadError;
@override
final void Function(InAppWebViewController controller, String url,
int statusCode, String description) onLoadHttpError;
@override
final void Function(
InAppWebViewController controller, LoadedResource resource)
onLoadResource;
@override
final Future<CustomSchemeResponse> Function(
InAppWebViewController controller, String scheme, String url)
onLoadResourceCustomScheme;
@override
final void Function(InAppWebViewController controller, String url)
onLoadStart;
@override
final void Function(InAppWebViewController controller, String url) onLoadStop;
@override
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
@override
final void Function(InAppWebViewController controller, String url) onPrint;
@override
final void Function(InAppWebViewController controller, int progress)
onProgressChanged;
@override
final Future<ClientCertResponse> Function(
InAppWebViewController controller, ClientCertChallenge challenge)
onReceivedClientCertRequest;
@override
final Future<HttpAuthResponse> Function(
InAppWebViewController controller, HttpAuthChallenge challenge)
onReceivedHttpAuthRequest;
@override
final Future<ServerTrustAuthResponse> Function(
InAppWebViewController controller, ServerTrustChallenge challenge)
onReceivedServerTrustAuthRequest;
@override
final void Function(InAppWebViewController controller, int x, int y)
onScrollChanged;
@override
final void Function(
InAppWebViewController controller, String url, bool androidIsReload)
onUpdateVisitedHistory;
@override
final void Function(InAppWebViewController controller) onWebViewCreated;
@override
final Future<AjaxRequest> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
shouldInterceptAjaxRequest;
@override
final Future<FetchRequest> Function(
InAppWebViewController controller, FetchRequest fetchRequest)
shouldInterceptFetchRequest;
@override
final Future<ShouldOverrideUrlLoadingAction> Function(
InAppWebViewController controller,
ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest)
shouldOverrideUrlLoading;
} }
class _InAppWebViewState extends State<InAppWebView> { class _InAppWebViewState extends State<InAppWebView> {
...@@ -390,16 +247,6 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -390,16 +247,6 @@ class _InAppWebViewState extends State<InAppWebView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Map<String, dynamic> initialOptions = {};
initialOptions
.addAll(widget.initialOptions.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid)
initialOptions.addAll(
widget.initialOptions.android?.toMap() ?? {});
else if (Platform.isIOS)
initialOptions
.addAll(widget.initialOptions.ios?.toMap() ?? {});
if (defaultTargetPlatform == TargetPlatform.android) { if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView( return AndroidView(
viewType: 'com.pichillilorenzo/flutter_inappwebview', viewType: 'com.pichillilorenzo/flutter_inappwebview',
...@@ -411,7 +258,7 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -411,7 +258,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile, 'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(), 'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders, 'initialHeaders': widget.initialHeaders,
'initialOptions': initialOptions 'initialOptions': widget.initialOptions?.toMap() ?? {}
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
...@@ -444,7 +291,7 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -444,7 +291,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile, 'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(), 'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders, 'initialHeaders': widget.initialHeaders,
'initialOptions': initialOptions 'initialOptions': widget.initialOptions?.toMap() ?? {}
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
...@@ -470,1302 +317,3 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -470,1302 +317,3 @@ class _InAppWebViewState extends State<InAppWebView> {
} }
} }
} }
/// Controls a WebView, such as an [InAppWebView] widget instance or [InAppBrowser] WebView instance.
///
/// If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated]
/// callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute.
class InAppWebViewController {
InAppWebView _widget;
MethodChannel _channel;
static MethodChannel _staticChannel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_static');
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>();
// ignore: unused_field
bool _isOpened = false;
// ignore: unused_field
int _id;
String _inAppBrowserUuid;
InAppBrowser _inAppBrowser;
///Android controller that contains only android-specific methods
AndroidInAppWebViewController android;
///iOS controller that contains only ios-specific methods
IOSInAppWebViewController ios;
InAppWebViewController(int id, InAppWebView widget) {
this._id = id;
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
this._channel.setMethodCallHandler(handleMethod);
this._widget = widget;
this.android = AndroidInAppWebViewController(this);
this.ios = IOSInAppWebViewController(this);
}
InAppWebViewController.fromInAppBrowser(
String uuid, MethodChannel channel, InAppBrowser inAppBrowser) {
this._inAppBrowserUuid = uuid;
this._channel = channel;
this._inAppBrowser = inAppBrowser;
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onLoadStart":
String url = call.arguments["url"];
if (_widget != null && _widget.onLoadStart != null)
_widget.onLoadStart(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onLoadStart(url);
break;
case "onLoadStop":
String url = call.arguments["url"];
if (_widget != null && _widget.onLoadStop != null)
_widget.onLoadStop(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onLoadStop(url);
break;
case "onLoadError":
String url = call.arguments["url"];
int code = call.arguments["code"];
String message = call.arguments["message"];
if (_widget != null && _widget.onLoadError != null)
_widget.onLoadError(this, url, code, message);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadError(url, code, message);
break;
case "onLoadHttpError":
String url = call.arguments["url"];
int statusCode = call.arguments["statusCode"];
String description = call.arguments["description"];
if (_widget != null && _widget.onLoadHttpError != null)
_widget.onLoadHttpError(this, url, statusCode, description);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadHttpError(url, statusCode, description);
break;
case "onProgressChanged":
int progress = call.arguments["progress"];
if (_widget != null && _widget.onProgressChanged != null)
_widget.onProgressChanged(this, progress);
else if (_inAppBrowser != null)
_inAppBrowser.onProgressChanged(progress);
break;
case "shouldOverrideUrlLoading":
String url = call.arguments["url"];
String method = call.arguments["method"];
Map<String, String> headers = call.arguments["headers"]?.cast<String, String>();
bool isForMainFrame = call.arguments["isForMainFrame"];
bool androidHasGesture = call.arguments["androidHasGesture"];
bool androidIsRedirect = call.arguments["androidIsRedirect"];
int iosWKNavigationType = call.arguments["iosWKNavigationType"];
ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest = ShouldOverrideUrlLoadingRequest(url: url, method: method, headers: headers, isForMainFrame: isForMainFrame,
androidHasGesture: androidHasGesture, androidIsRedirect: androidIsRedirect, iosWKNavigationType: IOSWKNavigationType.fromValue(iosWKNavigationType));
if (_widget != null && _widget.shouldOverrideUrlLoading != null)
return (await _widget.shouldOverrideUrlLoading(this, shouldOverrideUrlLoadingRequest))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.shouldOverrideUrlLoading(shouldOverrideUrlLoadingRequest))?.toMap();
break;
case "onConsoleMessage":
String message = call.arguments["message"];
ConsoleMessageLevel messageLevel =
ConsoleMessageLevel.fromValue(call.arguments["messageLevel"]);
ConsoleMessage consoleMessage =
ConsoleMessage(message: message, messageLevel: messageLevel);
if (_widget != null && _widget.onConsoleMessage != null)
_widget.onConsoleMessage(this, consoleMessage);
else if (_inAppBrowser != null)
_inAppBrowser.onConsoleMessage(consoleMessage);
break;
case "onScrollChanged":
int x = call.arguments["x"];
int y = call.arguments["y"];
if (_widget != null && _widget.onScrollChanged != null)
_widget.onScrollChanged(this, x, y);
else if (_inAppBrowser != null) _inAppBrowser.onScrollChanged(x, y);
break;
case "onDownloadStart":
String url = call.arguments["url"];
if (_widget != null && _widget.onDownloadStart != null)
_widget.onDownloadStart(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onDownloadStart(url);
break;
case "onLoadResourceCustomScheme":
String scheme = call.arguments["scheme"];
String url = call.arguments["url"];
if (_widget != null && _widget.onLoadResourceCustomScheme != null) {
try {
var response =
await _widget.onLoadResourceCustomScheme(this, scheme, url);
return (response != null) ? response.toJson() : null;
} catch (error) {
print(error);
return null;
}
} else if (_inAppBrowser != null) {
try {
var response =
await _inAppBrowser.onLoadResourceCustomScheme(scheme, url);
return (response != null) ? response.toJson() : null;
} catch (error) {
print(error);
return null;
}
}
break;
case "onCreateWindow":
String url = call.arguments["url"];
bool androidIsDialog = call.arguments["androidIsDialog"];
bool androidIsUserGesture = call.arguments["androidIsUserGesture"];
int iosWKNavigationType = call.arguments["iosWKNavigationType"];
OnCreateWindowRequest onCreateWindowRequest = OnCreateWindowRequest(url: url, androidIsDialog: androidIsDialog, androidIsUserGesture: androidIsUserGesture, iosWKNavigationType: IOSWKNavigationType.fromValue(iosWKNavigationType));
if (_widget != null && _widget.onCreateWindow != null)
_widget.onCreateWindow(this, onCreateWindowRequest);
else if (_inAppBrowser != null) _inAppBrowser.onCreateWindow(onCreateWindowRequest);
break;
case "onGeolocationPermissionsShowPrompt":
String origin = call.arguments["origin"];
if (_widget != null &&
_widget.androidOnGeolocationPermissionsShowPrompt != null)
return (await _widget.androidOnGeolocationPermissionsShowPrompt(
this, origin))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser
.androidOnGeolocationPermissionsShowPrompt(origin))
?.toMap();
break;
case "onGeolocationPermissionsHidePrompt":
if (_widget != null &&
_widget.androidOnGeolocationPermissionsHidePrompt != null)
await _widget.androidOnGeolocationPermissionsHidePrompt(
this);
else if (_inAppBrowser != null)
await _inAppBrowser
.androidOnGeolocationPermissionsHidePrompt();
break;
case "onJsAlert":
String message = call.arguments["message"];
if (_widget != null && _widget.onJsAlert != null)
return (await _widget.onJsAlert(this, message))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsAlert(message))?.toMap();
break;
case "onJsConfirm":
String message = call.arguments["message"];
if (_widget != null && _widget.onJsConfirm != null)
return (await _widget.onJsConfirm(this, message))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsConfirm(message))?.toMap();
break;
case "onJsPrompt":
String message = call.arguments["message"];
String defaultValue = call.arguments["defaultValue"];
if (_widget != null && _widget.onJsPrompt != null)
return (await _widget.onJsPrompt(this, message, defaultValue))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsPrompt(message, defaultValue))
?.toMap();
break;
case "onSafeBrowsingHit":
String url = call.arguments["url"];
SafeBrowsingThreat threatType =
SafeBrowsingThreat.fromValue(call.arguments["threatType"]);
if (_widget != null && _widget.androidOnSafeBrowsingHit != null)
return (await _widget.androidOnSafeBrowsingHit(this, url, threatType))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.androidOnSafeBrowsingHit(url, threatType))
?.toMap();
break;
case "onReceivedHttpAuthRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
int previousFailureCount = call.arguments["previousFailureCount"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = HttpAuthChallenge(
previousFailureCount: previousFailureCount,
protectionSpace: protectionSpace);
if (_widget != null && _widget.onReceivedHttpAuthRequest != null)
return (await _widget.onReceivedHttpAuthRequest(this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onReceivedHttpAuthRequest(challenge))
?.toMap();
break;
case "onReceivedServerTrustAuthRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
int error = call.arguments["error"];
String message = call.arguments["message"];
Uint8List serverCertificate = call.arguments["serverCertificate"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = ServerTrustChallenge(
protectionSpace: protectionSpace,
error: error,
message: message,
serverCertificate: serverCertificate);
if (_widget != null && _widget.onReceivedServerTrustAuthRequest != null)
return (await _widget.onReceivedServerTrustAuthRequest(
this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser
.onReceivedServerTrustAuthRequest(challenge))
?.toMap();
break;
case "onReceivedClientCertRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = ClientCertChallenge(protectionSpace: protectionSpace);
if (_widget != null && _widget.onReceivedClientCertRequest != null)
return (await _widget.onReceivedClientCertRequest(this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onReceivedClientCertRequest(challenge))
?.toMap();
break;
case "onFindResultReceived":
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
if (_widget != null && _widget.onFindResultReceived != null)
_widget.onFindResultReceived(
this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
else if (_inAppBrowser != null)
_inAppBrowser.onFindResultReceived(
activeMatchOrdinal, numberOfMatches, isDoneCounting);
break;
case "onPermissionRequest":
String origin = call.arguments["origin"];
List<String> resources = call.arguments["resources"].cast<String>();
if (_widget != null && _widget.androidOnPermissionRequest != null)
return (await _widget.androidOnPermissionRequest(this, origin, resources))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.androidOnPermissionRequest(origin, resources))
?.toMap();
break;
case "onUpdateVisitedHistory":
String url = call.arguments["url"];
bool androidIsReload = call.arguments["androidIsReload"];
if (_widget != null && _widget.onUpdateVisitedHistory != null)
_widget.onUpdateVisitedHistory(this, url, androidIsReload);
else if (_inAppBrowser != null)
_inAppBrowser.onUpdateVisitedHistory(url, androidIsReload);
return null;
case "onWebContentProcessDidTerminate":
if (_widget != null && _widget.iosOnWebContentProcessDidTerminate != null)
_widget.iosOnWebContentProcessDidTerminate(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnWebContentProcessDidTerminate();
return null;
case "onDidCommit":
if (_widget != null && _widget.iosOnDidCommit != null)
_widget.iosOnDidCommit(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnDidCommit();
return null;
case "onDidReceiveServerRedirectForProvisionalNavigation":
if (_widget != null && _widget.iosOnDidReceiveServerRedirectForProvisionalNavigation != null)
_widget.iosOnDidReceiveServerRedirectForProvisionalNavigation(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnDidReceiveServerRedirectForProvisionalNavigation();
return null;
case "onLongPressHitTestResult":
Map<dynamic, dynamic> hitTestResultMap = call.arguments["hitTestResult"];
LongPressHitTestResultType type = LongPressHitTestResultType.fromValue(hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"];
LongPressHitTestResult hitTestResult = new LongPressHitTestResult(type: type, extra: extra);
if (_widget != null && _widget.onLongPressHitTestResult != null)
_widget.onLongPressHitTestResult(this, hitTestResult);
else if (_inAppBrowser != null)
_inAppBrowser.onLongPressHitTestResult(hitTestResult);
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
List<dynamic> args = jsonDecode(call.arguments["args"]);
switch (handlerName) {
case "onLoadResource":
Map<dynamic, dynamic> argMap = args[0];
String initiatorType = argMap["initiatorType"];
String url = argMap["name"];
double startTime = argMap["startTime"] is int
? argMap["startTime"].toDouble()
: argMap["startTime"];
double duration = argMap["duration"] is int
? argMap["duration"].toDouble()
: argMap["duration"];
var response = new LoadedResource(
initiatorType: initiatorType,
url: url,
startTime: startTime,
duration: duration);
if (_widget != null && _widget.onLoadResource != null)
_widget.onLoadResource(this, response);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadResource(response);
return null;
case "shouldInterceptAjaxRequest":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
String responseType = argMap["responseType"];
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
responseType: responseType);
if (_widget != null && _widget.shouldInterceptAjaxRequest != null)
return jsonEncode(
await _widget.shouldInterceptAjaxRequest(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.shouldInterceptAjaxRequest(request));
return null;
case "onAjaxReadyStateChange":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
int readyState = argMap["readyState"];
int status = argMap["status"];
String responseURL = argMap["responseURL"];
String responseType = argMap["responseType"];
dynamic response = argMap["response"];
String responseText = argMap["responseText"];
String responseXML = argMap["responseXML"];
String statusText = argMap["statusText"];
Map<dynamic, dynamic> responseHeaders = argMap["responseHeaders"];
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
readyState: AjaxRequestReadyState.fromValue(readyState),
status: status,
responseURL: responseURL,
responseType: responseType,
response: response,
responseText: responseText,
responseXML: responseXML,
statusText: statusText,
responseHeaders: responseHeaders);
if (_widget != null && _widget.onAjaxReadyStateChange != null)
return jsonEncode(
await _widget.onAjaxReadyStateChange(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.onAjaxReadyStateChange(request));
return null;
case "onAjaxProgress":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
int readyState = argMap["readyState"];
int status = argMap["status"];
String responseURL = argMap["responseURL"];
String responseType = argMap["responseType"];
dynamic response = argMap["response"];
String responseText = argMap["responseText"];
String responseXML = argMap["responseXML"];
String statusText = argMap["statusText"];
Map<dynamic, dynamic> responseHeaders = argMap["responseHeaders"];
Map<dynamic, dynamic> eventMap = argMap["event"];
AjaxRequestEvent event = AjaxRequestEvent(
lengthComputable: eventMap["lengthComputable"],
loaded: eventMap["loaded"],
total: eventMap["total"],
type: AjaxRequestEventType.fromValue(eventMap["type"]));
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
readyState: AjaxRequestReadyState.fromValue(readyState),
status: status,
responseURL: responseURL,
responseType: responseType,
response: response,
responseText: responseText,
responseXML: responseXML,
statusText: statusText,
responseHeaders: responseHeaders,
event: event);
if (_widget != null && _widget.onAjaxProgress != null)
return jsonEncode(await _widget.onAjaxProgress(this, request));
else if (_inAppBrowser != null)
return jsonEncode(await _inAppBrowser.onAjaxProgress(request));
return null;
case "shouldInterceptFetchRequest":
Map<dynamic, dynamic> argMap = args[0];
String url = argMap["url"];
String method = argMap["method"];
Map<dynamic, dynamic> headers = argMap["headers"];
Uint8List body = Uint8List.fromList(argMap["body"].cast<int>());
String mode = argMap["mode"];
FetchRequestCredential credentials =
FetchRequest.createFetchRequestCredentialFromMap(
argMap["credentials"]);
String cache = argMap["cache"];
String redirect = argMap["redirect"];
String referrer = argMap["referrer"];
String referrerPolicy = argMap["referrerPolicy"];
String integrity = argMap["integrity"];
bool keepalive = argMap["keepalive"];
var request = new FetchRequest(
url: url,
method: method,
headers: headers,
body: body,
mode: mode,
credentials: credentials,
cache: cache,
redirect: redirect,
referrer: referrer,
referrerPolicy: referrerPolicy,
integrity: integrity,
keepalive: keepalive);
if (_widget != null && _widget.shouldInterceptFetchRequest != null)
return jsonEncode(
await _widget.shouldInterceptFetchRequest(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.shouldInterceptFetchRequest(request));
return null;
case "onPrint":
String url = args[0];
if (_widget != null && _widget.onPrint != null)
_widget.onPrint(this, url);
else if (_inAppBrowser != null)
_inAppBrowser.onPrint(url);
return null;
}
if (javaScriptHandlersMap.containsKey(handlerName)) {
// convert result to json
try {
return jsonEncode(await javaScriptHandlersMap[handlerName](args));
} catch (error) {
print(error);
return null;
}
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
///Gets the URL for the current page.
///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun, the current page may not have changed.
Future<String> getUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getUrl', args);
}
///Gets the title for the current page.
Future<String> getTitle() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getTitle', args);
}
///Gets the progress for the current page. The progress value is between 0 and 100.
Future<int> getProgress() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getProgress', args);
}
///Gets the content html of the page. It first tries to get the content through javascript.
///If this doesn't work, it tries to get the content reading the file:
///- checking if it is an asset (`file:///`) or
///- downloading it using an `HttpClient` through the WebView's current url.
Future<String> getHtml() async {
var html = "";
InAppWebViewWidgetOptions options = await getOptions();
if (options != null &&
options.crossPlatform.javaScriptEnabled == true) {
html = await evaluateJavascript(
source: "window.document.getElementsByTagName('html')[0].outerHTML;");
if (html != null && html.isNotEmpty) return html;
}
var webviewUrl = await getUrl();
if (webviewUrl.startsWith("file:///")) {
var assetPathSplitted = webviewUrl.split("/flutter_assets/");
var assetPath = assetPathSplitted[assetPathSplitted.length - 1];
var bytes = await rootBundle.load(assetPath);
html = utf8.decode(bytes.buffer.asUint8List());
} else {
HttpClient client = new HttpClient();
var url = Uri.parse(webviewUrl);
try {
var htmlRequest = await client.getUrl(url);
html =
await (await htmlRequest.close()).transform(Utf8Decoder()).join();
} catch (e) {
print(e);
}
}
return html;
}
///Gets the list of all favicons for the current page.
Future<List<Favicon>> getFavicons() async {
List<Favicon> favicons = [];
HttpClient client = new HttpClient();
var webviewUrl = await getUrl();
var url = (webviewUrl.startsWith("file:///"))
? Uri.file(webviewUrl)
: Uri.parse(webviewUrl);
String manifestUrl;
var html = await getHtml();
if (html.isEmpty) {
return favicons;
}
var assetPathBase;
if (webviewUrl.startsWith("file:///")) {
var assetPathSplitted = webviewUrl.split("/flutter_assets/");
assetPathBase = assetPathSplitted[0] + "/flutter_assets/";
}
// get all link html elements
var document = parse(html);
var links = document.getElementsByTagName('link');
for (var link in links) {
var attributes = link.attributes;
if (attributes["rel"] == "manifest") {
manifestUrl = attributes["href"];
if (!_isUrlAbsolute(manifestUrl)) {
if (manifestUrl.startsWith("/")) {
manifestUrl = manifestUrl.substring(1);
}
manifestUrl = ((assetPathBase == null)
? url.scheme + "://" + url.host + "/"
: assetPathBase) +
manifestUrl;
}
continue;
}
if (!attributes["rel"].contains("icon")) {
continue;
}
favicons.addAll(_createFavicons(url, assetPathBase, attributes["href"],
attributes["rel"], attributes["sizes"], false));
}
// try to get /favicon.ico
try {
var faviconUrl = url.scheme + "://" + url.host + "/favicon.ico";
await client.headUrl(Uri.parse(faviconUrl));
favicons.add(Favicon(url: faviconUrl, rel: "shortcut icon"));
} catch (e) {
print("/favicon.ico file not found: " + e.toString());
}
// try to get the manifest file
HttpClientRequest manifestRequest;
HttpClientResponse manifestResponse;
bool manifestFound = false;
if (manifestUrl == null) {
manifestUrl = url.scheme + "://" + url.host + "/manifest.json";
}
try {
manifestRequest = await client.getUrl(Uri.parse(manifestUrl));
manifestResponse = await manifestRequest.close();
manifestFound = manifestResponse.statusCode == 200 &&
manifestResponse.headers.contentType?.mimeType == "application/json";
} catch (e) {
print("Manifest file not found: " + e.toString());
}
if (manifestFound) {
Map<String, dynamic> manifest =
json.decode(await manifestResponse.transform(Utf8Decoder()).join());
if (manifest.containsKey("icons")) {
for (Map<String, dynamic> icon in manifest["icons"]) {
favicons.addAll(_createFavicons(url, assetPathBase, icon["src"],
icon["rel"], icon["sizes"], true));
}
}
}
return favicons;
}
bool _isUrlAbsolute(String url) {
return url.startsWith("http://") || url.startsWith("https://");
}
List<Favicon> _createFavicons(Uri url, String assetPathBase, String urlIcon,
String rel, String sizes, bool isManifest) {
List<Favicon> favicons = [];
List<String> urlSplitted = urlIcon.split("/");
if (!_isUrlAbsolute(urlIcon)) {
if (urlIcon.startsWith("/")) {
urlIcon = urlIcon.substring(1);
}
urlIcon = ((assetPathBase == null)
? url.scheme + "://" + url.host + "/"
: assetPathBase) +
urlIcon;
}
if (isManifest) {
rel = (sizes != null)
? urlSplitted[urlSplitted.length - 1]
.replaceFirst("-" + sizes, "")
.split(" ")[0]
.split(".")[0]
: null;
}
if (sizes != null && sizes.isNotEmpty && sizes != "any") {
List<String> sizesSplitted = sizes.split(" ");
for (String size in sizesSplitted) {
int width = int.parse(size.split("x")[0]);
int height = int.parse(size.split("x")[1]);
favicons
.add(Favicon(url: urlIcon, rel: rel, width: width, height: height));
}
} else {
favicons.add(Favicon(url: urlIcon, rel: rel, width: null, height: null));
}
return favicons;
}
///Loads the given [url] with optional [headers] specified as a map from name to value.
Future<void> loadUrl(
{@required String url, Map<String, String> headers = const {}}) async {
assert(url != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers);
await _channel.invokeMethod('loadUrl', args);
}
///Loads the given [url] with [postData] using `POST` method into this WebView.
Future<void> postUrl(
{@required String url, @required Uint8List postData}) async {
assert(url != null && url.isNotEmpty);
assert(postData != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('postData', () => postData);
await _channel.invokeMethod('postUrl', args);
}
///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content.
///
///The [mimeType] parameter specifies the format of the data. The default value is `"text/html"`.
///
///The [encoding] parameter specifies the encoding of the data. The default value is `"utf8"`.
///
///The [androidHistoryUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android.
Future<void> loadData(
{@required String data,
String mimeType = "text/html",
String encoding = "utf8",
String baseUrl = "about:blank",
String androidHistoryUrl = "about:blank"}) async {
assert(data != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('data', () => data);
args.putIfAbsent('mimeType', () => mimeType);
args.putIfAbsent('encoding', () => encoding);
args.putIfAbsent('baseUrl', () => baseUrl);
args.putIfAbsent('historyUrl', () => androidHistoryUrl);
await _channel.invokeMethod('loadData', args);
}
///Loads the given [assetFilePath] with optional [headers] specified as a map from name to value.
///
///To be able to load your local files (assets, js, css, etc.), you need to add them in the `assets` section of the `pubspec.yaml` file, otherwise they cannot be found!
///
///Example of a `pubspec.yaml` file:
///```yaml
///...
///
///# The following section is specific to Flutter.
///flutter:
///
/// # The following line ensures that the Material Icons font is
/// # included with your application, so that you can use the icons in
/// # the material Icons class.
/// uses-material-design: true
///
/// assets:
/// - assets/index.html
/// - assets/css/
/// - assets/images/
///
///...
///```
///Example of a `main.dart` file:
///```dart
///...
///inAppBrowser.loadFile("assets/index.html");
///...
///```
Future<void> loadFile(
{@required String assetFilePath,
Map<String, String> headers = const {}}) async {
assert(assetFilePath != null && assetFilePath.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers);
await _channel.invokeMethod('loadFile', args);
}
///Reloads the WebView.
Future<void> reload() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reload', args);
}
///Goes back in the history of the WebView.
Future<void> goBack() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goBack', args);
}
///Returns a boolean value indicating whether the WebView can move backward.
Future<bool> canGoBack() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canGoBack', args);
}
///Goes forward in the history of the WebView.
Future<void> goForward() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goForward', args);
}
///Returns a boolean value indicating whether the WebView can move forward.
Future<bool> canGoForward() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canGoForward', args);
}
///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward.
Future<void> goBackOrForward({@required int steps}) async {
assert(steps != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
await _channel.invokeMethod('goBackOrForward', args);
}
///Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward.
Future<bool> canGoBackOrForward({@required int steps}) async {
assert(steps != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
return await _channel.invokeMethod('canGoBackOrForward', args);
}
///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item.
Future<void> goTo({@required WebHistoryItem historyItem}) async {
await goBackOrForward(steps: historyItem.offset);
}
///Check if the WebView instance is in a loading state.
Future<bool> isLoading() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isLoading', args);
}
///Stops the WebView from loading.
Future<void> stopLoading() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('stopLoading', args);
}
///Evaluates JavaScript code into the WebView and returns the result of the evaluation.
Future<dynamic> evaluateJavascript({@required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
var data = await _channel.invokeMethod('evaluateJavascript', args);
if (data != null && Platform.isAndroid) data = json.decode(data);
return data;
}
///Injects an external JavaScript file into the WebView from a defined url.
Future<void> injectJavascriptFileFromUrl({@required String urlFile}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('urlFile', () => urlFile);
await _channel.invokeMethod('injectJavascriptFileFromUrl', args);
}
///Injects a JavaScript file into the WebView from the flutter assets directory.
Future<void> injectJavascriptFileFromAsset(
{@required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath);
await evaluateJavascript(source: source);
}
///Injects CSS into the WebView.
Future<void> injectCSSCode({@required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
await _channel.invokeMethod('injectCSSCode', args);
}
///Injects an external CSS file into the WebView from a defined url.
Future<void> injectCSSFileFromUrl({@required String urlFile}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('urlFile', () => urlFile);
await _channel.invokeMethod('injectStyleFile', args);
}
///Injects a CSS file into the WebView from the flutter assets directory.
Future<void> injectCSSFileFromAsset({@required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath);
await injectCSSCode(source: source);
}
///Adds a JavaScript message handler [callback] ([JavaScriptHandlerCallback]) that listen to post messages sent from JavaScript by the handler with name [handlerName].
///
///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 JavaScript function that can be used to call the handler is `window.flutter_inappwebview.callHandler(handlerName <String>, ...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.
///
///In order to call `window.flutter_inappwebview.callHandler(handlerName <String>, ...args)` properly, you need to wait and listen the JavaScript event `flutterInAppWebViewPlatformReady`.
///This event will be dispatched as soon as the platform (Android or iOS) is ready to handle the `callHandler` method.
///```javascript
/// window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
/// console.log("ready");
/// });
///```
///
///`window.flutter_inappwebview.callHandler` returns a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
///that can be used to get the json result returned by [JavaScriptHandlerCallback].
///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.
///
///So, on the JavaScript side, to get data coming from the Dart side, you will use:
///```html
///<script>
/// window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
/// window.flutter_inappwebview.callHandler('handlerFoo').then(function(result) {
/// console.log(result);
/// });
///
/// window.flutter_inappwebview.callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}).then(function(result) {
/// console.log(result);
/// });
/// });
///</script>
///```
///
///Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
///```dart
/// // Inject JavaScript that will receive data back from Flutter
/// inAppWebViewController.evaluateJavascript(source: """
/// window.flutter_inappwebview.callHandler('test', 'Text from Javascript').then(function(result) {
/// console.log(result);
/// });
/// """);
///```
///
///Forbidden names for JavaScript handlers are defined in [javaScriptHandlerForbiddenNames].
void addJavaScriptHandler(
{@required String handlerName,
@required JavaScriptHandlerCallback callback}) {
assert(!javaScriptHandlerForbiddenNames.contains(handlerName));
this.javaScriptHandlersMap[handlerName] = (callback);
}
///Removes a JavaScript message handler previously added with the [addJavaScriptHandler()] associated to [handlerName] key.
///Returns the value associated with [handlerName] before it was removed.
///Returns `null` if [handlerName] was not found.
JavaScriptHandlerCallback removeJavaScriptHandler(
{@required String handlerName}) {
return this.javaScriptHandlersMap.remove(handlerName);
}
///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it.
///
///**NOTE for iOS**: available from iOS 11.0+.
Future<Uint8List> takeScreenshot() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('takeScreenshot', args);
}
///Sets the WebView options with the new [options] and evaluates them.
Future<void> setOptions({@required InAppWebViewWidgetOptions options}) async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic> optionsMap = {};
optionsMap.addAll(options.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid)
optionsMap.addAll(options.android?.toMap() ?? {});
else if (Platform.isIOS)
optionsMap.addAll(options.ios?.toMap() ?? {});
args.putIfAbsent('options', () => optionsMap);
await _channel.invokeMethod('setOptions', args);
}
///Gets the current WebView options. Returns the options with `null` value if they are not set yet.
Future<InAppWebViewWidgetOptions> getOptions() async {
Map<String, dynamic> args = <String, dynamic>{};
InAppWebViewWidgetOptions inAppWebViewWidgetOptions =
InAppWebViewWidgetOptions();
Map<dynamic, dynamic> options =
await _channel.invokeMethod('getOptions', args);
if (options != null) {
options = options.cast<String, dynamic>();
inAppWebViewWidgetOptions.crossPlatform =
InAppWebViewOptions.fromMap(options);
if (Platform.isAndroid)
inAppWebViewWidgetOptions.android =
AndroidInAppWebViewOptions.fromMap(options);
else if (Platform.isIOS)
inAppWebViewWidgetOptions.ios =
IOSInAppWebViewOptions.fromMap(options);
}
return inAppWebViewWidgetOptions;
}
///Gets the WebHistory for this WebView. This contains the back/forward list for use in querying each item in the history stack.
///This contains only a snapshot of the current state.
///Multiple calls to this method may return different objects.
///The object returned from this method will not be updated to reflect any new state.
Future<WebHistory> getCopyBackForwardList() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result =
await _channel.invokeMethod('getCopyBackForwardList', args);
result = result.cast<String, dynamic>();
List<dynamic> historyListMap = result["history"];
historyListMap = historyListMap.cast<LinkedHashMap<dynamic, dynamic>>();
int currentIndex = result["currentIndex"];
List<WebHistoryItem> historyList = List();
for (var i = 0; i < historyListMap.length; i++) {
LinkedHashMap<dynamic, dynamic> historyItem = historyListMap[i];
historyList.add(WebHistoryItem(
originalUrl: historyItem["originalUrl"],
title: historyItem["title"],
url: historyItem["url"],
index: i,
offset: i - currentIndex));
}
return WebHistory(list: historyList, currentIndex: currentIndex);
}
///Clears all the webview's cache.
Future<void> clearCache() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearCache', args);
}
///Finds all instances of find on the page and highlights them. Notifies [onFindResultReceived] listener.
///
///[find] represents the string to find.
///
///**NOTE**: on Android, it finds all instances asynchronously. Successive calls to this will cancel any pending searches.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> findAllAsync({@required String find}) async {
assert(find != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await _channel.invokeMethod('findAllAsync', args);
}
///Highlights and scrolls to the next match found by [findAllAsync()]. Notifies [onFindResultReceived] listener.
///
///[forward] represents the direction to search.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> findNext({@required bool forward}) async {
assert(forward != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await _channel.invokeMethod('findNext', args);
}
///Clears the highlighting surrounding text matches created by [findAllAsync()].
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearMatches', args);
}
///Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with [getTRexRunnerCss()].
Future<String> getTRexRunnerHtml() async {
return await rootBundle
.loadString("packages/flutter_inappwebview/t_rex_runner/t-rex.html");
}
///Gets the css of the Chromium's t-rex runner game. Used in combination with [getTRexRunnerHtml()].
Future<String> getTRexRunnerCss() async {
return await rootBundle
.loadString("packages/flutter_inappwebview/t_rex_runner/t-rex.css");
}
///Scrolls the WebView to the position.
///
///[x] represents the x position to scroll to.
///
///[y] represents the y position to scroll to.
Future<void> scrollTo({@required int x, @required int y}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
await _channel.invokeMethod('scrollTo', args);
}
///Moves the scrolled position of the WebView.
///
///[x] represents the amount of pixels to scroll by horizontally.
///
///[y] represents the amount of pixels to scroll by vertically.
Future<void> scrollBy({@required int x, @required int y}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
await _channel.invokeMethod('scrollBy', args);
}
///On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews.
///This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused.
///
///On iOS, it is restricted to just this WebView.
Future<void> pauseTimers() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('pauseTimers', args);
}
///On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers.
///
///On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView.
Future<void> resumeTimers() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resumeTimers', args);
}
///Prints the current page.
///
///**NOTE**: available on Android 21+.
Future<void> printCurrentPage() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('printCurrentPage', args);
}
///Gets the height of the HTML content.
Future<int> getContentHeight() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getContentHeight', args);
}
///Gets the height of the HTML content.
///
///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 to 100.0 inclusive.
///
///**NOTE**: available on Android 21+.
Future<void> zoomBy(double zoomFactor) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('zoomFactor', () => zoomFactor);
return await _channel.invokeMethod('zoomBy', args);
}
///Gets the current scale of this WebView.
Future<double> getScale() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getScale', args);
}
///Gets the default user agent.
static Future<String> getDefaultUserAgent() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _staticChannel.invokeMethod('getDefaultUserAgent', args);
}
}
///InAppWebViewControllerAndroid class represents the Android controller that contains only android-specific methods for the WebView.
class AndroidInAppWebViewController {
InAppWebViewController _controller;
AndroidInAppWebViewController(InAppWebViewController controller) {
this._controller = controller;
}
///Starts Safe Browsing initialization.
///
///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true.
///Safe Browsing is not fully supported on all devices. For those devices this method will returns false.
///
///This should not be called if Safe Browsing has been disabled by manifest tag
///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing.
///
///**NOTE**: available only on Android 27+.
Future<bool> startSafeBrowsing() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('startSafeBrowsing', args);
}
///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews.
///
/// Each rule should take one of these:
///| Rule | Example | Matches Subdomain |
///| -- | -- | -- |
///| HOSTNAME | example.com | Yes |
///| .HOSTNAME | .example.com | No |
///| IPV4_LITERAL | 192.168.1.1 | No |
///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No |
///
///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
///
///[hosts] represents the list of hosts. This value must never be null.
///
///**NOTE**: available only on Android 27+.
Future<bool> setSafeBrowsingWhitelist({@required List<String> hosts}) async {
assert(hosts != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('hosts', () => hosts);
return await _controller._channel.invokeMethod('setSafeBrowsingWhitelist', args);
}
///Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`.
///
///**NOTE**: available only on Android 27+.
Future<String> getSafeBrowsingPrivacyPolicyUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('getSafeBrowsingPrivacyPolicyUrl', args);
}
///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors.
Future<void> clearSslPreferences() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('clearSslPreferences', args);
}
///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests.
///Note that WebView automatically clears these preferences when the system keychain is updated.
///The preferences are shared by all the WebViews that are created by the embedder application.
///
///**NOTE**: On iOS certificate-based credentials are never stored permanently.
///
///**NOTE**: available on Android 21+.
Future<void> clearClientCertPreferences() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('clearClientCertPreferences', args);
}
///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript.
///To pause JavaScript globally, use [pauseTimers()]. To resume WebView, call [resume()].
Future<void> pause() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('pause', args);
}
///Resumes a WebView after a previous call to [pause()].
Future<void> resume() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('resume', args);
}
///Gets the URL that was originally requested for the current page.
///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun,
///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested.
Future<String> getOriginalUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('getOriginalUrl', args);
}
}
///InAppWebViewControllerIOS class represents the iOS controller that contains only ios-specific methods for the WebView.
class IOSInAppWebViewController {
InAppWebViewController _controller;
IOSInAppWebViewController(InAppWebViewController controller) {
this._controller = controller;
}
///Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
Future<void> reloadFromOrigin() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('reloadFromOrigin', args);
}
///A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections.
Future<bool> hasOnlySecureContent() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('hasOnlySecureContent', args);
}
}
\ No newline at end of file
import 'dart:io';
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_inappwebview/src/webview.dart';
import 'package:html/parser.dart' show parse;
import 'types.dart';
import 'in_app_browser.dart';
import 'webview_options.dart';
///Controls a WebView, such as an [InAppWebView] widget instance, a [HeadlessInAppWebView] instance or [InAppBrowser] WebView instance.
///
///If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated]
///callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute.
class InAppWebViewController {
WebView _webview;
MethodChannel _channel;
static MethodChannel _staticChannel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_static');
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>();
// ignore: unused_field
bool _isOpened = false;
// ignore: unused_field
dynamic _id;
String _inAppBrowserUuid;
InAppBrowser _inAppBrowser;
///Android controller that contains only android-specific methods
AndroidInAppWebViewController android;
///iOS controller that contains only ios-specific methods
IOSInAppWebViewController ios;
InAppWebViewController(dynamic id, WebView webview) {
this._id = id;
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
this._channel.setMethodCallHandler(handleMethod);
this._webview = webview;
this.android = AndroidInAppWebViewController(this);
this.ios = IOSInAppWebViewController(this);
}
InAppWebViewController.fromInAppBrowser(
String uuid, MethodChannel channel, InAppBrowser inAppBrowser) {
this._inAppBrowserUuid = uuid;
this._channel = channel;
this._inAppBrowser = inAppBrowser;
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onHeadlessWebViewCreated":
if (_webview != null && _webview is HeadlessInAppWebView)
_webview.onWebViewCreated(this);
break;
case "onLoadStart":
String url = call.arguments["url"];
if (_webview != null && _webview.onLoadStart != null)
_webview.onLoadStart(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onLoadStart(url);
break;
case "onLoadStop":
String url = call.arguments["url"];
if (_webview != null && _webview.onLoadStop != null)
_webview.onLoadStop(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onLoadStop(url);
break;
case "onLoadError":
String url = call.arguments["url"];
int code = call.arguments["code"];
String message = call.arguments["message"];
if (_webview != null && _webview.onLoadError != null)
_webview.onLoadError(this, url, code, message);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadError(url, code, message);
break;
case "onLoadHttpError":
String url = call.arguments["url"];
int statusCode = call.arguments["statusCode"];
String description = call.arguments["description"];
if (_webview != null && _webview.onLoadHttpError != null)
_webview.onLoadHttpError(this, url, statusCode, description);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadHttpError(url, statusCode, description);
break;
case "onProgressChanged":
int progress = call.arguments["progress"];
if (_webview != null && _webview.onProgressChanged != null)
_webview.onProgressChanged(this, progress);
else if (_inAppBrowser != null)
_inAppBrowser.onProgressChanged(progress);
break;
case "shouldOverrideUrlLoading":
String url = call.arguments["url"];
String method = call.arguments["method"];
Map<String, String> headers =
call.arguments["headers"]?.cast<String, String>();
bool isForMainFrame = call.arguments["isForMainFrame"];
bool androidHasGesture = call.arguments["androidHasGesture"];
bool androidIsRedirect = call.arguments["androidIsRedirect"];
int iosWKNavigationType = call.arguments["iosWKNavigationType"];
ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest =
ShouldOverrideUrlLoadingRequest(
url: url,
method: method,
headers: headers,
isForMainFrame: isForMainFrame,
androidHasGesture: androidHasGesture,
androidIsRedirect: androidIsRedirect,
iosWKNavigationType:
IOSWKNavigationType.fromValue(iosWKNavigationType));
if (_webview != null && _webview.shouldOverrideUrlLoading != null)
return (await _webview.shouldOverrideUrlLoading(
this, shouldOverrideUrlLoadingRequest))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser
.shouldOverrideUrlLoading(shouldOverrideUrlLoadingRequest))
?.toMap();
break;
case "onConsoleMessage":
String message = call.arguments["message"];
ConsoleMessageLevel messageLevel =
ConsoleMessageLevel.fromValue(call.arguments["messageLevel"]);
ConsoleMessage consoleMessage =
ConsoleMessage(message: message, messageLevel: messageLevel);
if (_webview != null && _webview.onConsoleMessage != null)
_webview.onConsoleMessage(this, consoleMessage);
else if (_inAppBrowser != null)
_inAppBrowser.onConsoleMessage(consoleMessage);
break;
case "onScrollChanged":
int x = call.arguments["x"];
int y = call.arguments["y"];
if (_webview != null && _webview.onScrollChanged != null)
_webview.onScrollChanged(this, x, y);
else if (_inAppBrowser != null) _inAppBrowser.onScrollChanged(x, y);
break;
case "onDownloadStart":
String url = call.arguments["url"];
if (_webview != null && _webview.onDownloadStart != null)
_webview.onDownloadStart(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onDownloadStart(url);
break;
case "onLoadResourceCustomScheme":
String scheme = call.arguments["scheme"];
String url = call.arguments["url"];
if (_webview != null && _webview.onLoadResourceCustomScheme != null) {
try {
var response =
await _webview.onLoadResourceCustomScheme(this, scheme, url);
return (response != null) ? response.toJson() : null;
} catch (error) {
print(error);
return null;
}
} else if (_inAppBrowser != null) {
try {
var response =
await _inAppBrowser.onLoadResourceCustomScheme(scheme, url);
return (response != null) ? response.toJson() : null;
} catch (error) {
print(error);
return null;
}
}
break;
case "onCreateWindow":
String url = call.arguments["url"];
bool androidIsDialog = call.arguments["androidIsDialog"];
bool androidIsUserGesture = call.arguments["androidIsUserGesture"];
int iosWKNavigationType = call.arguments["iosWKNavigationType"];
OnCreateWindowRequest onCreateWindowRequest = OnCreateWindowRequest(
url: url,
androidIsDialog: androidIsDialog,
androidIsUserGesture: androidIsUserGesture,
iosWKNavigationType:
IOSWKNavigationType.fromValue(iosWKNavigationType));
if (_webview != null && _webview.onCreateWindow != null)
_webview.onCreateWindow(this, onCreateWindowRequest);
else if (_inAppBrowser != null)
_inAppBrowser.onCreateWindow(onCreateWindowRequest);
break;
case "onGeolocationPermissionsShowPrompt":
String origin = call.arguments["origin"];
if (_webview != null &&
_webview.androidOnGeolocationPermissionsShowPrompt != null)
return (await _webview.androidOnGeolocationPermissionsShowPrompt(
this, origin))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser
.androidOnGeolocationPermissionsShowPrompt(origin))
?.toMap();
break;
case "onGeolocationPermissionsHidePrompt":
if (_webview != null &&
_webview.androidOnGeolocationPermissionsHidePrompt != null)
await _webview.androidOnGeolocationPermissionsHidePrompt(this);
else if (_inAppBrowser != null)
await _inAppBrowser.androidOnGeolocationPermissionsHidePrompt();
break;
case "onJsAlert":
String message = call.arguments["message"];
if (_webview != null && _webview.onJsAlert != null)
return (await _webview.onJsAlert(this, message))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsAlert(message))?.toMap();
break;
case "onJsConfirm":
String message = call.arguments["message"];
if (_webview != null && _webview.onJsConfirm != null)
return (await _webview.onJsConfirm(this, message))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsConfirm(message))?.toMap();
break;
case "onJsPrompt":
String message = call.arguments["message"];
String defaultValue = call.arguments["defaultValue"];
if (_webview != null && _webview.onJsPrompt != null)
return (await _webview.onJsPrompt(this, message, defaultValue))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsPrompt(message, defaultValue))
?.toMap();
break;
case "onSafeBrowsingHit":
String url = call.arguments["url"];
SafeBrowsingThreat threatType =
SafeBrowsingThreat.fromValue(call.arguments["threatType"]);
if (_webview != null && _webview.androidOnSafeBrowsingHit != null)
return (await _webview.androidOnSafeBrowsingHit(this, url, threatType))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.androidOnSafeBrowsingHit(url, threatType))
?.toMap();
break;
case "onReceivedHttpAuthRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
int previousFailureCount = call.arguments["previousFailureCount"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = HttpAuthChallenge(
previousFailureCount: previousFailureCount,
protectionSpace: protectionSpace);
if (_webview != null && _webview.onReceivedHttpAuthRequest != null)
return (await _webview.onReceivedHttpAuthRequest(this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onReceivedHttpAuthRequest(challenge))
?.toMap();
break;
case "onReceivedServerTrustAuthRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
int error = call.arguments["error"];
String message = call.arguments["message"];
Uint8List serverCertificate = call.arguments["serverCertificate"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = ServerTrustChallenge(
protectionSpace: protectionSpace,
error: error,
message: message,
serverCertificate: serverCertificate);
if (_webview != null && _webview.onReceivedServerTrustAuthRequest != null)
return (await _webview.onReceivedServerTrustAuthRequest(
this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser
.onReceivedServerTrustAuthRequest(challenge))
?.toMap();
break;
case "onReceivedClientCertRequest":
String host = call.arguments["host"];
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = ClientCertChallenge(protectionSpace: protectionSpace);
if (_webview != null && _webview.onReceivedClientCertRequest != null)
return (await _webview.onReceivedClientCertRequest(this, challenge))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onReceivedClientCertRequest(challenge))
?.toMap();
break;
case "onFindResultReceived":
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
if (_webview != null && _webview.onFindResultReceived != null)
_webview.onFindResultReceived(
this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
else if (_inAppBrowser != null)
_inAppBrowser.onFindResultReceived(
activeMatchOrdinal, numberOfMatches, isDoneCounting);
break;
case "onPermissionRequest":
String origin = call.arguments["origin"];
List<String> resources = call.arguments["resources"].cast<String>();
if (_webview != null && _webview.androidOnPermissionRequest != null)
return (await _webview.androidOnPermissionRequest(
this, origin, resources))
?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.androidOnPermissionRequest(
origin, resources))
?.toMap();
break;
case "onUpdateVisitedHistory":
String url = call.arguments["url"];
bool androidIsReload = call.arguments["androidIsReload"];
if (_webview != null && _webview.onUpdateVisitedHistory != null)
_webview.onUpdateVisitedHistory(this, url, androidIsReload);
else if (_inAppBrowser != null)
_inAppBrowser.onUpdateVisitedHistory(url, androidIsReload);
return null;
case "onWebContentProcessDidTerminate":
if (_webview != null &&
_webview.iosOnWebContentProcessDidTerminate != null)
_webview.iosOnWebContentProcessDidTerminate(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnWebContentProcessDidTerminate();
return null;
case "onDidCommit":
if (_webview != null && _webview.iosOnDidCommit != null)
_webview.iosOnDidCommit(this);
else if (_inAppBrowser != null) _inAppBrowser.iosOnDidCommit();
return null;
case "onDidReceiveServerRedirectForProvisionalNavigation":
if (_webview != null &&
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation !=
null)
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnDidReceiveServerRedirectForProvisionalNavigation();
return null;
case "onLongPressHitTestResult":
Map<dynamic, dynamic> hitTestResultMap =
call.arguments["hitTestResult"];
LongPressHitTestResultType type = LongPressHitTestResultType.fromValue(
hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"];
LongPressHitTestResult hitTestResult =
new LongPressHitTestResult(type: type, extra: extra);
if (_webview != null && _webview.onLongPressHitTestResult != null)
_webview.onLongPressHitTestResult(this, hitTestResult);
else if (_inAppBrowser != null)
_inAppBrowser.onLongPressHitTestResult(hitTestResult);
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
List<dynamic> args = jsonDecode(call.arguments["args"]);
switch (handlerName) {
case "onLoadResource":
Map<dynamic, dynamic> argMap = args[0];
String initiatorType = argMap["initiatorType"];
String url = argMap["name"];
double startTime = argMap["startTime"] is int
? argMap["startTime"].toDouble()
: argMap["startTime"];
double duration = argMap["duration"] is int
? argMap["duration"].toDouble()
: argMap["duration"];
var response = new LoadedResource(
initiatorType: initiatorType,
url: url,
startTime: startTime,
duration: duration);
if (_webview != null && _webview.onLoadResource != null)
_webview.onLoadResource(this, response);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadResource(response);
return null;
case "shouldInterceptAjaxRequest":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
String responseType = argMap["responseType"];
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
responseType: responseType);
if (_webview != null && _webview.shouldInterceptAjaxRequest != null)
return jsonEncode(
await _webview.shouldInterceptAjaxRequest(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.shouldInterceptAjaxRequest(request));
return null;
case "onAjaxReadyStateChange":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
int readyState = argMap["readyState"];
int status = argMap["status"];
String responseURL = argMap["responseURL"];
String responseType = argMap["responseType"];
dynamic response = argMap["response"];
String responseText = argMap["responseText"];
String responseXML = argMap["responseXML"];
String statusText = argMap["statusText"];
Map<dynamic, dynamic> responseHeaders = argMap["responseHeaders"];
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
readyState: AjaxRequestReadyState.fromValue(readyState),
status: status,
responseURL: responseURL,
responseType: responseType,
response: response,
responseText: responseText,
responseXML: responseXML,
statusText: statusText,
responseHeaders: responseHeaders);
if (_webview != null && _webview.onAjaxReadyStateChange != null)
return jsonEncode(
await _webview.onAjaxReadyStateChange(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.onAjaxReadyStateChange(request));
return null;
case "onAjaxProgress":
Map<dynamic, dynamic> argMap = args[0];
dynamic data = argMap["data"];
String method = argMap["method"];
String url = argMap["url"];
bool isAsync = argMap["isAsync"];
String user = argMap["user"];
String password = argMap["password"];
bool withCredentials = argMap["withCredentials"];
AjaxRequestHeaders headers = AjaxRequestHeaders(argMap["headers"]);
int readyState = argMap["readyState"];
int status = argMap["status"];
String responseURL = argMap["responseURL"];
String responseType = argMap["responseType"];
dynamic response = argMap["response"];
String responseText = argMap["responseText"];
String responseXML = argMap["responseXML"];
String statusText = argMap["statusText"];
Map<dynamic, dynamic> responseHeaders = argMap["responseHeaders"];
Map<dynamic, dynamic> eventMap = argMap["event"];
AjaxRequestEvent event = AjaxRequestEvent(
lengthComputable: eventMap["lengthComputable"],
loaded: eventMap["loaded"],
total: eventMap["total"],
type: AjaxRequestEventType.fromValue(eventMap["type"]));
var request = new AjaxRequest(
data: data,
method: method,
url: url,
isAsync: isAsync,
user: user,
password: password,
withCredentials: withCredentials,
headers: headers,
readyState: AjaxRequestReadyState.fromValue(readyState),
status: status,
responseURL: responseURL,
responseType: responseType,
response: response,
responseText: responseText,
responseXML: responseXML,
statusText: statusText,
responseHeaders: responseHeaders,
event: event);
if (_webview != null && _webview.onAjaxProgress != null)
return jsonEncode(await _webview.onAjaxProgress(this, request));
else if (_inAppBrowser != null)
return jsonEncode(await _inAppBrowser.onAjaxProgress(request));
return null;
case "shouldInterceptFetchRequest":
Map<dynamic, dynamic> argMap = args[0];
String url = argMap["url"];
String method = argMap["method"];
Map<dynamic, dynamic> headers = argMap["headers"];
Uint8List body = Uint8List.fromList(argMap["body"].cast<int>());
String mode = argMap["mode"];
FetchRequestCredential credentials =
FetchRequest.createFetchRequestCredentialFromMap(
argMap["credentials"]);
String cache = argMap["cache"];
String redirect = argMap["redirect"];
String referrer = argMap["referrer"];
String referrerPolicy = argMap["referrerPolicy"];
String integrity = argMap["integrity"];
bool keepalive = argMap["keepalive"];
var request = new FetchRequest(
url: url,
method: method,
headers: headers,
body: body,
mode: mode,
credentials: credentials,
cache: cache,
redirect: redirect,
referrer: referrer,
referrerPolicy: referrerPolicy,
integrity: integrity,
keepalive: keepalive);
if (_webview != null && _webview.shouldInterceptFetchRequest != null)
return jsonEncode(
await _webview.shouldInterceptFetchRequest(this, request));
else if (_inAppBrowser != null)
return jsonEncode(
await _inAppBrowser.shouldInterceptFetchRequest(request));
return null;
case "onPrint":
String url = args[0];
if (_webview != null && _webview.onPrint != null)
_webview.onPrint(this, url);
else if (_inAppBrowser != null) _inAppBrowser.onPrint(url);
return null;
}
if (javaScriptHandlersMap.containsKey(handlerName)) {
// convert result to json
try {
return jsonEncode(await javaScriptHandlersMap[handlerName](args));
} catch (error) {
print(error);
return null;
}
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
///Gets the URL for the current page.
///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun, the current page may not have changed.
Future<String> getUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getUrl', args);
}
///Gets the title for the current page.
Future<String> getTitle() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getTitle', args);
}
///Gets the progress for the current page. The progress value is between 0 and 100.
Future<int> getProgress() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getProgress', args);
}
///Gets the content html of the page. It first tries to get the content through javascript.
///If this doesn't work, it tries to get the content reading the file:
///- checking if it is an asset (`file:///`) or
///- downloading it using an `HttpClient` through the WebView's current url.
Future<String> getHtml() async {
var html = "";
InAppWebViewGroupOptions options = await getOptions();
if (options != null && options.crossPlatform.javaScriptEnabled == true) {
html = await evaluateJavascript(
source: "window.document.getElementsByTagName('html')[0].outerHTML;");
if (html != null && html.isNotEmpty) return html;
}
var webviewUrl = await getUrl();
if (webviewUrl.startsWith("file:///")) {
var assetPathSplitted = webviewUrl.split("/flutter_assets/");
var assetPath = assetPathSplitted[assetPathSplitted.length - 1];
var bytes = await rootBundle.load(assetPath);
html = utf8.decode(bytes.buffer.asUint8List());
} else {
HttpClient client = new HttpClient();
var url = Uri.parse(webviewUrl);
try {
var htmlRequest = await client.getUrl(url);
html =
await (await htmlRequest.close()).transform(Utf8Decoder()).join();
} catch (e) {
print(e);
}
}
return html;
}
///Gets the list of all favicons for the current page.
Future<List<Favicon>> getFavicons() async {
List<Favicon> favicons = [];
HttpClient client = new HttpClient();
var webviewUrl = await getUrl();
var url = (webviewUrl.startsWith("file:///"))
? Uri.file(webviewUrl)
: Uri.parse(webviewUrl);
String manifestUrl;
var html = await getHtml();
if (html.isEmpty) {
return favicons;
}
var assetPathBase;
if (webviewUrl.startsWith("file:///")) {
var assetPathSplitted = webviewUrl.split("/flutter_assets/");
assetPathBase = assetPathSplitted[0] + "/flutter_assets/";
}
// get all link html elements
var document = parse(html);
var links = document.getElementsByTagName('link');
for (var link in links) {
var attributes = link.attributes;
if (attributes["rel"] == "manifest") {
manifestUrl = attributes["href"];
if (!_isUrlAbsolute(manifestUrl)) {
if (manifestUrl.startsWith("/")) {
manifestUrl = manifestUrl.substring(1);
}
manifestUrl = ((assetPathBase == null)
? url.scheme + "://" + url.host + "/"
: assetPathBase) +
manifestUrl;
}
continue;
}
if (!attributes["rel"].contains("icon")) {
continue;
}
favicons.addAll(_createFavicons(url, assetPathBase, attributes["href"],
attributes["rel"], attributes["sizes"], false));
}
// try to get /favicon.ico
try {
var faviconUrl = url.scheme + "://" + url.host + "/favicon.ico";
await client.headUrl(Uri.parse(faviconUrl));
favicons.add(Favicon(url: faviconUrl, rel: "shortcut icon"));
} catch (e) {
print("/favicon.ico file not found: " + e.toString());
}
// try to get the manifest file
HttpClientRequest manifestRequest;
HttpClientResponse manifestResponse;
bool manifestFound = false;
if (manifestUrl == null) {
manifestUrl = url.scheme + "://" + url.host + "/manifest.json";
}
try {
manifestRequest = await client.getUrl(Uri.parse(manifestUrl));
manifestResponse = await manifestRequest.close();
manifestFound = manifestResponse.statusCode == 200 &&
manifestResponse.headers.contentType?.mimeType == "application/json";
} catch (e) {
print("Manifest file not found: " + e.toString());
}
if (manifestFound) {
Map<String, dynamic> manifest =
json.decode(await manifestResponse.transform(Utf8Decoder()).join());
if (manifest.containsKey("icons")) {
for (Map<String, dynamic> icon in manifest["icons"]) {
favicons.addAll(_createFavicons(url, assetPathBase, icon["src"],
icon["rel"], icon["sizes"], true));
}
}
}
return favicons;
}
bool _isUrlAbsolute(String url) {
return url.startsWith("http://") || url.startsWith("https://");
}
List<Favicon> _createFavicons(Uri url, String assetPathBase, String urlIcon,
String rel, String sizes, bool isManifest) {
List<Favicon> favicons = [];
List<String> urlSplitted = urlIcon.split("/");
if (!_isUrlAbsolute(urlIcon)) {
if (urlIcon.startsWith("/")) {
urlIcon = urlIcon.substring(1);
}
urlIcon = ((assetPathBase == null)
? url.scheme + "://" + url.host + "/"
: assetPathBase) +
urlIcon;
}
if (isManifest) {
rel = (sizes != null)
? urlSplitted[urlSplitted.length - 1]
.replaceFirst("-" + sizes, "")
.split(" ")[0]
.split(".")[0]
: null;
}
if (sizes != null && sizes.isNotEmpty && sizes != "any") {
List<String> sizesSplitted = sizes.split(" ");
for (String size in sizesSplitted) {
int width = int.parse(size.split("x")[0]);
int height = int.parse(size.split("x")[1]);
favicons
.add(Favicon(url: urlIcon, rel: rel, width: width, height: height));
}
} else {
favicons.add(Favicon(url: urlIcon, rel: rel, width: null, height: null));
}
return favicons;
}
///Loads the given [url] with optional [headers] specified as a map from name to value.
Future<void> loadUrl(
{@required String url, Map<String, String> headers = const {}}) async {
assert(url != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers);
await _channel.invokeMethod('loadUrl', args);
}
///Loads the given [url] with [postData] using `POST` method into this WebView.
Future<void> postUrl(
{@required String url, @required Uint8List postData}) async {
assert(url != null && url.isNotEmpty);
assert(postData != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('postData', () => postData);
await _channel.invokeMethod('postUrl', args);
}
///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content.
///
///The [mimeType] parameter specifies the format of the data. The default value is `"text/html"`.
///
///The [encoding] parameter specifies the encoding of the data. The default value is `"utf8"`.
///
///The [androidHistoryUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android.
Future<void> loadData(
{@required String data,
String mimeType = "text/html",
String encoding = "utf8",
String baseUrl = "about:blank",
String androidHistoryUrl = "about:blank"}) async {
assert(data != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('data', () => data);
args.putIfAbsent('mimeType', () => mimeType);
args.putIfAbsent('encoding', () => encoding);
args.putIfAbsent('baseUrl', () => baseUrl);
args.putIfAbsent('historyUrl', () => androidHistoryUrl);
await _channel.invokeMethod('loadData', args);
}
///Loads the given [assetFilePath] with optional [headers] specified as a map from name to value.
///
///To be able to load your local files (assets, js, css, etc.), you need to add them in the `assets` section of the `pubspec.yaml` file, otherwise they cannot be found!
///
///Example of a `pubspec.yaml` file:
///```yaml
///...
///
///# The following section is specific to Flutter.
///flutter:
///
/// # The following line ensures that the Material Icons font is
/// # included with your application, so that you can use the icons in
/// # the material Icons class.
/// uses-material-design: true
///
/// assets:
/// - assets/index.html
/// - assets/css/
/// - assets/images/
///
///...
///```
///Example of a `main.dart` file:
///```dart
///...
///inAppBrowser.loadFile("assets/index.html");
///...
///```
Future<void> loadFile(
{@required String assetFilePath,
Map<String, String> headers = const {}}) async {
assert(assetFilePath != null && assetFilePath.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers);
await _channel.invokeMethod('loadFile', args);
}
///Reloads the WebView.
Future<void> reload() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reload', args);
}
///Goes back in the history of the WebView.
Future<void> goBack() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goBack', args);
}
///Returns a boolean value indicating whether the WebView can move backward.
Future<bool> canGoBack() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canGoBack', args);
}
///Goes forward in the history of the WebView.
Future<void> goForward() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goForward', args);
}
///Returns a boolean value indicating whether the WebView can move forward.
Future<bool> canGoForward() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canGoForward', args);
}
///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward.
Future<void> goBackOrForward({@required int steps}) async {
assert(steps != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
await _channel.invokeMethod('goBackOrForward', args);
}
///Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward.
Future<bool> canGoBackOrForward({@required int steps}) async {
assert(steps != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
return await _channel.invokeMethod('canGoBackOrForward', args);
}
///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item.
Future<void> goTo({@required WebHistoryItem historyItem}) async {
await goBackOrForward(steps: historyItem.offset);
}
///Check if the WebView instance is in a loading state.
Future<bool> isLoading() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isLoading', args);
}
///Stops the WebView from loading.
Future<void> stopLoading() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('stopLoading', args);
}
///Evaluates JavaScript code into the WebView and returns the result of the evaluation.
Future<dynamic> evaluateJavascript({@required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
var data = await _channel.invokeMethod('evaluateJavascript', args);
if (data != null && Platform.isAndroid) data = json.decode(data);
return data;
}
///Injects an external JavaScript file into the WebView from a defined url.
Future<void> injectJavascriptFileFromUrl({@required String urlFile}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('urlFile', () => urlFile);
await _channel.invokeMethod('injectJavascriptFileFromUrl', args);
}
///Injects a JavaScript file into the WebView from the flutter assets directory.
Future<void> injectJavascriptFileFromAsset(
{@required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath);
await evaluateJavascript(source: source);
}
///Injects CSS into the WebView.
Future<void> injectCSSCode({@required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
await _channel.invokeMethod('injectCSSCode', args);
}
///Injects an external CSS file into the WebView from a defined url.
Future<void> injectCSSFileFromUrl({@required String urlFile}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('urlFile', () => urlFile);
await _channel.invokeMethod('injectStyleFile', args);
}
///Injects a CSS file into the WebView from the flutter assets directory.
Future<void> injectCSSFileFromAsset({@required String assetFilePath}) async {
String source = await rootBundle.loadString(assetFilePath);
await injectCSSCode(source: source);
}
///Adds a JavaScript message handler [callback] ([JavaScriptHandlerCallback]) that listen to post messages sent from JavaScript by the handler with name [handlerName].
///
///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 JavaScript function that can be used to call the handler is `window.flutter_inappwebview.callHandler(handlerName <String>, ...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.
///
///In order to call `window.flutter_inappwebview.callHandler(handlerName <String>, ...args)` properly, you need to wait and listen the JavaScript event `flutterInAppWebViewPlatformReady`.
///This event will be dispatched as soon as the platform (Android or iOS) is ready to handle the `callHandler` method.
///```javascript
/// window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
/// console.log("ready");
/// });
///```
///
///`window.flutter_inappwebview.callHandler` returns a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
///that can be used to get the json result returned by [JavaScriptHandlerCallback].
///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.
///
///So, on the JavaScript side, to get data coming from the Dart side, you will use:
///```html
///<script>
/// window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
/// window.flutter_inappwebview.callHandler('handlerFoo').then(function(result) {
/// console.log(result);
/// });
///
/// window.flutter_inappwebview.callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}).then(function(result) {
/// console.log(result);
/// });
/// });
///</script>
///```
///
///Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
///```dart
/// // Inject JavaScript that will receive data back from Flutter
/// inAppWebViewController.evaluateJavascript(source: """
/// window.flutter_inappwebview.callHandler('test', 'Text from Javascript').then(function(result) {
/// console.log(result);
/// });
/// """);
///```
///
///Forbidden names for JavaScript handlers are defined in [javaScriptHandlerForbiddenNames].
void addJavaScriptHandler(
{@required String handlerName,
@required JavaScriptHandlerCallback callback}) {
assert(!javaScriptHandlerForbiddenNames.contains(handlerName));
this.javaScriptHandlersMap[handlerName] = (callback);
}
///Removes a JavaScript message handler previously added with the [addJavaScriptHandler()] associated to [handlerName] key.
///Returns the value associated with [handlerName] before it was removed.
///Returns `null` if [handlerName] was not found.
JavaScriptHandlerCallback removeJavaScriptHandler(
{@required String handlerName}) {
return this.javaScriptHandlersMap.remove(handlerName);
}
///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it.
///
///**NOTE for iOS**: available from iOS 11.0+.
Future<Uint8List> takeScreenshot() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('takeScreenshot', args);
}
///Sets the WebView options with the new [options] and evaluates them.
Future<void> setOptions({@required InAppWebViewGroupOptions options}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('options', () => options?.toMap());
await _channel.invokeMethod('setOptions', args);
}
///Gets the current WebView options. Returns the options with `null` value if they are not set yet.
Future<InAppWebViewGroupOptions> getOptions() async {
Map<String, dynamic> args = <String, dynamic>{};
InAppWebViewGroupOptions inAppWebViewGroupOptions =
InAppWebViewGroupOptions();
Map<dynamic, dynamic> options =
await _channel.invokeMethod('getOptions', args);
if (options != null) {
options = options.cast<String, dynamic>();
inAppWebViewGroupOptions.crossPlatform =
InAppWebViewOptions.fromMap(options);
if (Platform.isAndroid)
inAppWebViewGroupOptions.android =
AndroidInAppWebViewOptions.fromMap(options);
else if (Platform.isIOS)
inAppWebViewGroupOptions.ios = IOSInAppWebViewOptions.fromMap(options);
}
return inAppWebViewGroupOptions;
}
///Gets the WebHistory for this WebView. This contains the back/forward list for use in querying each item in the history stack.
///This contains only a snapshot of the current state.
///Multiple calls to this method may return different objects.
///The object returned from this method will not be updated to reflect any new state.
Future<WebHistory> getCopyBackForwardList() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result =
await _channel.invokeMethod('getCopyBackForwardList', args);
result = result.cast<String, dynamic>();
List<dynamic> historyListMap = result["history"];
historyListMap = historyListMap.cast<LinkedHashMap<dynamic, dynamic>>();
int currentIndex = result["currentIndex"];
List<WebHistoryItem> historyList = List();
for (var i = 0; i < historyListMap.length; i++) {
LinkedHashMap<dynamic, dynamic> historyItem = historyListMap[i];
historyList.add(WebHistoryItem(
originalUrl: historyItem["originalUrl"],
title: historyItem["title"],
url: historyItem["url"],
index: i,
offset: i - currentIndex));
}
return WebHistory(list: historyList, currentIndex: currentIndex);
}
///Clears all the webview's cache.
Future<void> clearCache() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearCache', args);
}
///Finds all instances of find on the page and highlights them. Notifies [onFindResultReceived] listener.
///
///[find] represents the string to find.
///
///**NOTE**: on Android, it finds all instances asynchronously. Successive calls to this will cancel any pending searches.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> findAllAsync({@required String find}) async {
assert(find != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await _channel.invokeMethod('findAllAsync', args);
}
///Highlights and scrolls to the next match found by [findAllAsync()]. Notifies [onFindResultReceived] listener.
///
///[forward] represents the direction to search.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> findNext({@required bool forward}) async {
assert(forward != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await _channel.invokeMethod('findNext', args);
}
///Clears the highlighting surrounding text matches created by [findAllAsync()].
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearMatches', args);
}
///Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with [getTRexRunnerCss()].
Future<String> getTRexRunnerHtml() async {
return await rootBundle
.loadString("packages/flutter_inappwebview/t_rex_runner/t-rex.html");
}
///Gets the css of the Chromium's t-rex runner game. Used in combination with [getTRexRunnerHtml()].
Future<String> getTRexRunnerCss() async {
return await rootBundle
.loadString("packages/flutter_inappwebview/t_rex_runner/t-rex.css");
}
///Scrolls the WebView to the position.
///
///[x] represents the x position to scroll to.
///
///[y] represents the y position to scroll to.
Future<void> scrollTo({@required int x, @required int y}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
await _channel.invokeMethod('scrollTo', args);
}
///Moves the scrolled position of the WebView.
///
///[x] represents the amount of pixels to scroll by horizontally.
///
///[y] represents the amount of pixels to scroll by vertically.
Future<void> scrollBy({@required int x, @required int y}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
await _channel.invokeMethod('scrollBy', args);
}
///On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews.
///This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused.
///
///On iOS, it is restricted to just this WebView.
Future<void> pauseTimers() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('pauseTimers', args);
}
///On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers.
///
///On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView.
Future<void> resumeTimers() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resumeTimers', args);
}
///Prints the current page.
///
///**NOTE**: available on Android 21+.
Future<void> printCurrentPage() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('printCurrentPage', args);
}
///Gets the height of the HTML content.
Future<int> getContentHeight() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getContentHeight', args);
}
///Gets the height of the HTML content.
///
///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 to 100.0 inclusive.
///
///**NOTE**: available on Android 21+.
Future<void> zoomBy(double zoomFactor) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('zoomFactor', () => zoomFactor);
return await _channel.invokeMethod('zoomBy', args);
}
///Gets the current scale of this WebView.
Future<double> getScale() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getScale', args);
}
///Gets the default user agent.
static Future<String> getDefaultUserAgent() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _staticChannel.invokeMethod('getDefaultUserAgent', args);
}
}
///InAppWebViewControllerAndroid class represents the Android controller that contains only android-specific methods for the WebView.
class AndroidInAppWebViewController {
InAppWebViewController _controller;
AndroidInAppWebViewController(InAppWebViewController controller) {
this._controller = controller;
}
///Starts Safe Browsing initialization.
///
///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true.
///Safe Browsing is not fully supported on all devices. For those devices this method will returns false.
///
///This should not be called if Safe Browsing has been disabled by manifest tag
///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing.
///
///**NOTE**: available only on Android 27+.
Future<bool> startSafeBrowsing() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('startSafeBrowsing', args);
}
///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews.
///
/// Each rule should take one of these:
///| Rule | Example | Matches Subdomain |
///| -- | -- | -- |
///| HOSTNAME | example.com | Yes |
///| .HOSTNAME | .example.com | No |
///| IPV4_LITERAL | 192.168.1.1 | No |
///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No |
///
///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
///
///[hosts] represents the list of hosts. This value must never be null.
///
///**NOTE**: available only on Android 27+.
Future<bool> setSafeBrowsingWhitelist({@required List<String> hosts}) async {
assert(hosts != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('hosts', () => hosts);
return await _controller._channel
.invokeMethod('setSafeBrowsingWhitelist', args);
}
///Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`.
///
///**NOTE**: available only on Android 27+.
Future<String> getSafeBrowsingPrivacyPolicyUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel
.invokeMethod('getSafeBrowsingPrivacyPolicyUrl', args);
}
///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors.
Future<void> clearSslPreferences() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('clearSslPreferences', args);
}
///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests.
///Note that WebView automatically clears these preferences when the system keychain is updated.
///The preferences are shared by all the WebViews that are created by the embedder application.
///
///**NOTE**: On iOS certificate-based credentials are never stored permanently.
///
///**NOTE**: available on Android 21+.
Future<void> clearClientCertPreferences() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('clearClientCertPreferences', args);
}
///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript.
///To pause JavaScript globally, use [pauseTimers()]. To resume WebView, call [resume()].
Future<void> pause() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('pause', args);
}
///Resumes a WebView after a previous call to [pause()].
Future<void> resume() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('resume', args);
}
///Gets the URL that was originally requested for the current page.
///This is not always the same as the URL passed to [InAppWebView.onLoadStarted] because although the load for that URL has begun,
///the current page may not have changed. Also, there may have been redirects resulting in a different URL to that originally requested.
Future<String> getOriginalUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel.invokeMethod('getOriginalUrl', args);
}
}
///InAppWebViewControllerIOS class represents the iOS controller that contains only ios-specific methods for the WebView.
class IOSInAppWebViewController {
InAppWebViewController _controller;
IOSInAppWebViewController(InAppWebViewController controller) {
this._controller = controller;
}
///Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
Future<void> reloadFromOrigin() async {
Map<String, dynamic> args = <String, dynamic>{};
await _controller._channel.invokeMethod('reloadFromOrigin', args);
}
///A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections.
Future<bool> hasOnlySecureContent() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _controller._channel
.invokeMethod('hasOnlySecureContent', args);
}
}
import 'dart:async'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'webview_options.dart'; import 'webview_options.dart';
...@@ -183,8 +182,6 @@ class ConsoleMessage { ...@@ -183,8 +182,6 @@ class ConsoleMessage {
{this.message = "", this.messageLevel = ConsoleMessageLevel.LOG}); {this.message = "", this.messageLevel = ConsoleMessageLevel.LOG});
} }
///WebHistory class.
///
///This class contains a snapshot of the current back/forward list for a WebView. ///This class contains a snapshot of the current back/forward list for a WebView.
class WebHistory { class WebHistory {
///List of all [WebHistoryItem]s. ///List of all [WebHistoryItem]s.
...@@ -196,8 +193,6 @@ class WebHistory { ...@@ -196,8 +193,6 @@ class WebHistory {
WebHistory({this.list, this.currentIndex}); WebHistory({this.list, this.currentIndex});
} }
///WebHistoryItem class.
///
///A convenience class for accessing fields in an entry in the back/forward list of a WebView. Each WebHistoryItem is a snapshot of the requested history item. ///A convenience class for accessing fields in an entry in the back/forward list of a WebView. Each WebHistoryItem is a snapshot of the requested history item.
class WebHistoryItem { class WebHistoryItem {
///Original url of this history item. ///Original url of this history item.
...@@ -219,8 +214,6 @@ class WebHistoryItem { ...@@ -219,8 +214,6 @@ class WebHistoryItem {
{this.originalUrl, this.title, this.url, this.index, this.offset}); {this.originalUrl, this.title, this.url, this.index, this.offset});
} }
///GeolocationPermissionPromptResponse class.
///
///Class used by the host application to set the Geolocation permission state for an origin during the [androidOnGeolocationPermissionsShowPrompt] event. ///Class used by the host application to set the Geolocation permission state for an origin during the [androidOnGeolocationPermissionsShowPrompt] event.
class GeolocationPermissionShowPromptResponse { class GeolocationPermissionShowPromptResponse {
///The origin for which permissions are set. ///The origin for which permissions are set.
...@@ -240,7 +233,7 @@ class GeolocationPermissionShowPromptResponse { ...@@ -240,7 +233,7 @@ class GeolocationPermissionShowPromptResponse {
} }
} }
///JsAlertResponseAction class used by [JsAlertResponse] class. ///Class used by [JsAlertResponse] class.
class JsAlertResponseAction { class JsAlertResponseAction {
final int _value; final int _value;
const JsAlertResponseAction._internal(this._value); const JsAlertResponseAction._internal(this._value);
...@@ -254,7 +247,7 @@ class JsAlertResponseAction { ...@@ -254,7 +247,7 @@ class JsAlertResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///JsAlertResponse class represents the response used by the [onJsAlert] event to control a JavaScript alert dialog. ///Class represents the response used by the [onJsAlert] event to control a JavaScript alert dialog.
class JsAlertResponse { class JsAlertResponse {
///Message to be displayed in the window. ///Message to be displayed in the window.
String message; String message;
...@@ -284,7 +277,7 @@ class JsAlertResponse { ...@@ -284,7 +277,7 @@ class JsAlertResponse {
} }
} }
///JsConfirmResponseAction class used by [JsConfirmResponse] class. ///Class used by [JsConfirmResponse] class.
class JsConfirmResponseAction { class JsConfirmResponseAction {
final int _value; final int _value;
const JsConfirmResponseAction._internal(this._value); const JsConfirmResponseAction._internal(this._value);
...@@ -299,7 +292,7 @@ class JsConfirmResponseAction { ...@@ -299,7 +292,7 @@ class JsConfirmResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///JsConfirmResponse class represents the response used by the [onJsConfirm] event to control a JavaScript confirm dialog. ///Class that represents the response used by the [onJsConfirm] event to control a JavaScript confirm dialog.
class JsConfirmResponse { class JsConfirmResponse {
///Message to be displayed in the window. ///Message to be displayed in the window.
String message; String message;
...@@ -334,7 +327,7 @@ class JsConfirmResponse { ...@@ -334,7 +327,7 @@ class JsConfirmResponse {
} }
} }
///JsPromptResponseAction class used by [JsPromptResponse] class. ///Class used by [JsPromptResponse] class.
class JsPromptResponseAction { class JsPromptResponseAction {
final int _value; final int _value;
const JsPromptResponseAction._internal(this._value); const JsPromptResponseAction._internal(this._value);
...@@ -349,7 +342,7 @@ class JsPromptResponseAction { ...@@ -349,7 +342,7 @@ class JsPromptResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///JsPromptResponse class represents the response used by the [onJsPrompt] event to control a JavaScript prompt dialog. ///Class that represents the response used by the [onJsPrompt] event to control a JavaScript prompt dialog.
class JsPromptResponse { class JsPromptResponse {
///Message to be displayed in the window. ///Message to be displayed in the window.
String message; String message;
...@@ -394,7 +387,7 @@ class JsPromptResponse { ...@@ -394,7 +387,7 @@ class JsPromptResponse {
} }
} }
///SafeBrowsingThreat class represents the reason the resource was caught by Safe Browsing. ///Class that represents the reason the resource was caught by Safe Browsing.
class SafeBrowsingThreat { class SafeBrowsingThreat {
final int _value; final int _value;
const SafeBrowsingThreat._internal(this._value); const SafeBrowsingThreat._internal(this._value);
...@@ -438,7 +431,7 @@ class SafeBrowsingThreat { ...@@ -438,7 +431,7 @@ class SafeBrowsingThreat {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///SafeBrowsingResponseAction class used by [SafeBrowsingResponse] class. ///Class used by [SafeBrowsingResponse] class.
class SafeBrowsingResponseAction { class SafeBrowsingResponseAction {
final int _value; final int _value;
const SafeBrowsingResponseAction._internal(this._value); const SafeBrowsingResponseAction._internal(this._value);
...@@ -460,7 +453,7 @@ class SafeBrowsingResponseAction { ...@@ -460,7 +453,7 @@ class SafeBrowsingResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///SafeBrowsingResponse class represents the response used by the [androidOnSafeBrowsingHit] event. ///Class that represents the response used by the [androidOnSafeBrowsingHit] event.
///It is used to indicate an action to take when hitting a malicious URL. ///It is used to indicate an action to take when hitting a malicious URL.
class SafeBrowsingResponse { class SafeBrowsingResponse {
///If reporting is enabled, all reports will be sent according to the privacy policy referenced by [InAppWebViewController.androidGetSafeBrowsingPrivacyPolicyUrl]. ///If reporting is enabled, all reports will be sent according to the privacy policy referenced by [InAppWebViewController.androidGetSafeBrowsingPrivacyPolicyUrl].
...@@ -478,7 +471,7 @@ class SafeBrowsingResponse { ...@@ -478,7 +471,7 @@ class SafeBrowsingResponse {
} }
} }
///HttpAuthResponseAction class used by [HttpAuthResponse] class. ///Class used by [HttpAuthResponse] class.
class HttpAuthResponseAction { class HttpAuthResponseAction {
final int _value; final int _value;
const HttpAuthResponseAction._internal(this._value); const HttpAuthResponseAction._internal(this._value);
...@@ -500,7 +493,7 @@ class HttpAuthResponseAction { ...@@ -500,7 +493,7 @@ class HttpAuthResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///HttpAuthResponse class represents the response used by the [onReceivedHttpAuthRequest] event. ///Class that represents the response used by the [onReceivedHttpAuthRequest] event.
class HttpAuthResponse { class HttpAuthResponse {
///Represents the username used for the authentication if the [action] corresponds to [HttpAuthResponseAction.PROCEED] ///Represents the username used for the authentication if the [action] corresponds to [HttpAuthResponseAction.PROCEED]
String username; String username;
...@@ -530,7 +523,7 @@ class HttpAuthResponse { ...@@ -530,7 +523,7 @@ class HttpAuthResponse {
} }
} }
///HttpAuthChallenge class represents the challenge of the [onReceivedHttpAuthRequest] event. ///Class that represents the challenge of the [onReceivedHttpAuthRequest] event.
///It provides all the information about the challenge. ///It provides all the information about the challenge.
class HttpAuthChallenge { class HttpAuthChallenge {
///A count of previous failed authentication attempts. ///A count of previous failed authentication attempts.
...@@ -544,7 +537,7 @@ class HttpAuthChallenge { ...@@ -544,7 +537,7 @@ class HttpAuthChallenge {
: assert(previousFailureCount != null && protectionSpace != null); : assert(previousFailureCount != null && protectionSpace != null);
} }
///ProtectionSpace class represents a protection space requiring authentication. ///Class that represents a protection space requiring authentication.
class ProtectionSpace { class ProtectionSpace {
///The hostname of the server. ///The hostname of the server.
String host; String host;
...@@ -565,7 +558,7 @@ class ProtectionSpace { ...@@ -565,7 +558,7 @@ class ProtectionSpace {
: assert(host != null && protocol != null); : assert(host != null && protocol != null);
} }
///HttpAuthCredential represents the credentials of an http authentication. ///Class that represents the credentials of an http authentication.
///It is used by the [HttpAuthCredentialDatabase] class. ///It is used by the [HttpAuthCredentialDatabase] class.
class HttpAuthCredential { class HttpAuthCredential {
///Represents the username. ///Represents the username.
...@@ -578,7 +571,7 @@ class HttpAuthCredential { ...@@ -578,7 +571,7 @@ class HttpAuthCredential {
: assert(username != null && password != null); : assert(username != null && password != null);
} }
///ServerTrustAuthResponseAction class used by [ServerTrustAuthResponse] class. ///Class used by [ServerTrustAuthResponse] class.
class ServerTrustAuthResponseAction { class ServerTrustAuthResponseAction {
final int _value; final int _value;
const ServerTrustAuthResponseAction._internal(this._value); const ServerTrustAuthResponseAction._internal(this._value);
...@@ -608,7 +601,7 @@ class ServerTrustAuthResponse { ...@@ -608,7 +601,7 @@ class ServerTrustAuthResponse {
} }
} }
///ServerTrustChallenge class represents the challenge of the [onReceivedServerTrustAuthRequest] event. ///Class that represents the challenge of the [onReceivedServerTrustAuthRequest] event.
///It provides all the information about the challenge. ///It provides all the information about the challenge.
class ServerTrustChallenge { class ServerTrustChallenge {
///The protection space requiring authentication. ///The protection space requiring authentication.
...@@ -635,7 +628,7 @@ class ServerTrustChallenge { ...@@ -635,7 +628,7 @@ class ServerTrustChallenge {
: assert(protectionSpace != null && error != null); : assert(protectionSpace != null && error != null);
} }
///ClientCertResponseAction class used by [ClientCertResponse] class. ///Class used by [ClientCertResponse] class.
class ClientCertResponseAction { class ClientCertResponseAction {
final int _value; final int _value;
const ClientCertResponseAction._internal(this._value); const ClientCertResponseAction._internal(this._value);
...@@ -656,7 +649,7 @@ class ClientCertResponseAction { ...@@ -656,7 +649,7 @@ class ClientCertResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///ClientCertResponse class represents the response used by the [onReceivedClientCertRequest] event. ///Class that represents the response used by the [onReceivedClientCertRequest] event.
class ClientCertResponse { class ClientCertResponse {
///The file path of the certificate to use. ///The file path of the certificate to use.
String certificatePath; String certificatePath;
...@@ -689,7 +682,7 @@ class ClientCertResponse { ...@@ -689,7 +682,7 @@ class ClientCertResponse {
} }
} }
///ClientCertChallenge class represents the challenge of the [onReceivedClientCertRequest] event. ///Class that represents the challenge of the [onReceivedClientCertRequest] event.
///It provides all the information about the challenge. ///It provides all the information about the challenge.
class ClientCertChallenge { class ClientCertChallenge {
///The protection space requiring authentication. ///The protection space requiring authentication.
...@@ -699,7 +692,7 @@ class ClientCertChallenge { ...@@ -699,7 +692,7 @@ class ClientCertChallenge {
: assert(protectionSpace != null); : assert(protectionSpace != null);
} }
///Favicon class represents a favicon of a website. It is used by [InAppWebViewController.getFavicons] method. ///Class that represents a favicon of a website. It is used by [InAppWebViewController.getFavicons] method.
class Favicon { class Favicon {
///The url of the favicon image. ///The url of the favicon image.
String url; String url;
...@@ -721,7 +714,7 @@ class Favicon { ...@@ -721,7 +714,7 @@ class Favicon {
} }
} }
///AndroidCacheMode class represents an Android-specific class used to override the way the cache is used. ///Class that represents an Android-specific class used to override the way the cache is used.
class AndroidCacheMode { class AndroidCacheMode {
final int _value; final int _value;
const AndroidCacheMode._internal(this._value); const AndroidCacheMode._internal(this._value);
...@@ -768,7 +761,7 @@ class AndroidCacheMode { ...@@ -768,7 +761,7 @@ class AndroidCacheMode {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AndroidActionModeMenuItem class represents an Android-specific class used to disable the action mode menu items. ///Class that represents an Android-specific class used to disable the action mode menu items.
/// ///
///**NOTE**: available on Android 24+. ///**NOTE**: available on Android 24+.
class AndroidActionModeMenuItem { class AndroidActionModeMenuItem {
...@@ -818,7 +811,7 @@ class AndroidActionModeMenuItem { ...@@ -818,7 +811,7 @@ class AndroidActionModeMenuItem {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AndroidForceDark class represents an Android-specific class used to indicate the force dark mode. ///Class that represents an Android-specific class used to indicate the force dark mode.
/// ///
///**NOTE**: available on Android 29+. ///**NOTE**: available on Android 29+.
class AndroidForceDark { class AndroidForceDark {
...@@ -861,7 +854,7 @@ class AndroidForceDark { ...@@ -861,7 +854,7 @@ class AndroidForceDark {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AndroidLayoutAlgorithm class represents an Android-specific class used to set the underlying layout algorithm. ///Class that represents an Android-specific class used to set the underlying layout algorithm.
class AndroidLayoutAlgorithm { class AndroidLayoutAlgorithm {
final String _value; final String _value;
const AndroidLayoutAlgorithm._internal(this._value); const AndroidLayoutAlgorithm._internal(this._value);
...@@ -892,7 +885,7 @@ class AndroidLayoutAlgorithm { ...@@ -892,7 +885,7 @@ class AndroidLayoutAlgorithm {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AndroidMixedContentMode class represents an Android-specific class used to configure the WebView's behavior when a secure origin attempts to load a resource from an insecure origin. ///Class that represents an Android-specific class used to configure the WebView's behavior when a secure origin attempts to load a resource from an insecure origin.
/// ///
///**NOTE**: available on Android 21+. ///**NOTE**: available on Android 21+.
class AndroidMixedContentMode { class AndroidMixedContentMode {
...@@ -942,7 +935,7 @@ class AndroidMixedContentMode { ...@@ -942,7 +935,7 @@ class AndroidMixedContentMode {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSWKSelectionGranularity class represents an iOS-specific class used to set the level of granularity with which the user can interactively select content in the web view. ///Class that represents an iOS-specific class used to set the level of granularity with which the user can interactively select content in the web view.
class IOSWKSelectionGranularity { class IOSWKSelectionGranularity {
final int _value; final int _value;
const IOSWKSelectionGranularity._internal(this._value); const IOSWKSelectionGranularity._internal(this._value);
...@@ -977,7 +970,7 @@ class IOSWKSelectionGranularity { ...@@ -977,7 +970,7 @@ class IOSWKSelectionGranularity {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSWKDataDetectorTypes class represents an iOS-specific class used to specify a dataDetectoryTypes value that adds interactivity to web content that matches the value. ///Class that represents an iOS-specific class used to specify a dataDetectoryTypes value that adds interactivity to web content that matches the value.
/// ///
///**NOTE**: available on iOS 10.0+. ///**NOTE**: available on iOS 10.0+.
class IOSWKDataDetectorTypes { class IOSWKDataDetectorTypes {
...@@ -1048,7 +1041,7 @@ class IOSWKDataDetectorTypes { ...@@ -1048,7 +1041,7 @@ class IOSWKDataDetectorTypes {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSUIScrollViewDecelerationRate class represents a floating-point value that determines the rate of deceleration after the user lifts their finger. ///Class that represents a floating-point value that determines the rate of deceleration after the user lifts their finger.
class IOSUIScrollViewDecelerationRate { class IOSUIScrollViewDecelerationRate {
final String _value; final String _value;
const IOSUIScrollViewDecelerationRate._internal(this._value); const IOSUIScrollViewDecelerationRate._internal(this._value);
...@@ -1077,7 +1070,7 @@ class IOSUIScrollViewDecelerationRate { ...@@ -1077,7 +1070,7 @@ class IOSUIScrollViewDecelerationRate {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///UserPreferredContentMode class represents the content mode to prefer when loading and rendering a webpage. ///Class that represents the content mode to prefer when loading and rendering a webpage.
class UserPreferredContentMode { class UserPreferredContentMode {
final int _value; final int _value;
const UserPreferredContentMode._internal(this._value); const UserPreferredContentMode._internal(this._value);
...@@ -1118,7 +1111,7 @@ class UserPreferredContentMode { ...@@ -1118,7 +1111,7 @@ class UserPreferredContentMode {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSUIModalPresentationStyle class represents an iOS-specific class used to specify the modal presentation style when presenting a view controller. ///Class that represents an iOS-specific class used to specify the modal presentation style when presenting a view controller.
class IOSUIModalPresentationStyle { class IOSUIModalPresentationStyle {
final int _value; final int _value;
const IOSUIModalPresentationStyle._internal(this._value); const IOSUIModalPresentationStyle._internal(this._value);
...@@ -1201,7 +1194,7 @@ class IOSUIModalPresentationStyle { ...@@ -1201,7 +1194,7 @@ class IOSUIModalPresentationStyle {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSUIModalTransitionStyle class represents an iOS-specific class used to specify the transition style when presenting a view controller. ///Class that represents an iOS-specific class used to specify the transition style when presenting a view controller.
class IOSUIModalTransitionStyle { class IOSUIModalTransitionStyle {
final int _value; final int _value;
const IOSUIModalTransitionStyle._internal(this._value); const IOSUIModalTransitionStyle._internal(this._value);
...@@ -1255,7 +1248,7 @@ class IOSUIModalTransitionStyle { ...@@ -1255,7 +1248,7 @@ class IOSUIModalTransitionStyle {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///IOSSafariDismissButtonStyle class represents an iOS-specific class used to set the custom style for the dismiss button. ///Class that represents an iOS-specific class used to set the custom style for the dismiss button.
/// ///
///**NOTE**: available on iOS 11.0+. ///**NOTE**: available on iOS 11.0+.
class IOSSafariDismissButtonStyle { class IOSSafariDismissButtonStyle {
...@@ -1296,8 +1289,8 @@ class IOSSafariDismissButtonStyle { ...@@ -1296,8 +1289,8 @@ class IOSSafariDismissButtonStyle {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///InAppWebViewWidgetOptions class represents the options that can be used for an [InAppWebView]. ///Class that represents the options that can be used for a [WebView].
class InAppWebViewWidgetOptions { class InAppWebViewGroupOptions {
///Cross-platform options. ///Cross-platform options.
InAppWebViewOptions crossPlatform; InAppWebViewOptions crossPlatform;
...@@ -1307,13 +1300,28 @@ class InAppWebViewWidgetOptions { ...@@ -1307,13 +1300,28 @@ class InAppWebViewWidgetOptions {
///iOS-specific options. ///iOS-specific options.
IOSInAppWebViewOptions ios; IOSInAppWebViewOptions ios;
InAppWebViewWidgetOptions( InAppWebViewGroupOptions(
{this.crossPlatform, {this.crossPlatform,
this.android, this.android,
this.ios}); this.ios});
Map<String, dynamic> toMap() {
Map<String, dynamic> options = {};
options.addAll(this.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid)
options.addAll(this.android?.toMap() ?? {});
else if (Platform.isIOS)
options.addAll(this.ios?.toMap() ?? {});
return options;
}
Map<String, dynamic> toJson() {
return this.toMap();
}
} }
///InAppBrowserClassOptions class represents the options that can be used for an [InAppBrowser] WebView. ///Class that represents the options that can be used for an [InAppBrowser] WebView.
class InAppBrowserClassOptions { class InAppBrowserClassOptions {
///Cross-platform options. ///Cross-platform options.
InAppBrowserOptions crossPlatform; InAppBrowserOptions crossPlatform;
...@@ -1325,16 +1333,42 @@ class InAppBrowserClassOptions { ...@@ -1325,16 +1333,42 @@ class InAppBrowserClassOptions {
IOSInAppBrowserOptions ios; IOSInAppBrowserOptions ios;
///WebView options. ///WebView options.
InAppWebViewWidgetOptions inAppWebViewWidgetOptions; InAppWebViewGroupOptions inAppWebViewGroupOptions;
InAppBrowserClassOptions( InAppBrowserClassOptions(
{this.crossPlatform, {this.crossPlatform,
this.android, this.android,
this.ios, this.ios,
this.inAppWebViewWidgetOptions}); this.inAppWebViewGroupOptions});
Map<String, dynamic> toMap() {
Map<String, dynamic> options = {};
options.addAll(this.crossPlatform?.toMap() ?? {});
options.addAll(
this.inAppWebViewGroupOptions?.crossPlatform?.toMap() ?? {});
if (Platform.isAndroid) {
options.addAll(this.android?.toMap() ?? {});
options.addAll(this
.inAppWebViewGroupOptions?.android
?.toMap() ??
{});
} else if (Platform.isIOS) {
options.addAll(this.ios?.toMap() ?? {});
options.addAll(
this.inAppWebViewGroupOptions?.ios?.toMap() ??
{});
}
return options;
}
Map<String, dynamic> toJson() {
return this.toMap();
}
} }
///ChromeSafariBrowserClassOptions class represents the options that can be used for an [ChromeSafariBrowser] window. ///Class that represents the options that can be used for an [ChromeSafariBrowser] window.
class ChromeSafariBrowserClassOptions { class ChromeSafariBrowserClassOptions {
///Android-specific options. ///Android-specific options.
AndroidChromeCustomTabsOptions android; AndroidChromeCustomTabsOptions android;
...@@ -1344,9 +1378,23 @@ class ChromeSafariBrowserClassOptions { ...@@ -1344,9 +1378,23 @@ class ChromeSafariBrowserClassOptions {
ChromeSafariBrowserClassOptions( ChromeSafariBrowserClassOptions(
{this.android, this.ios}); {this.android, this.ios});
Map<String, dynamic> toMap() {
Map<String, dynamic> options = {};
if (Platform.isAndroid)
options.addAll(this.android?.toMap() ?? {});
else if (Platform.isIOS)
options.addAll(this.ios?.toMap() ?? {});
return options;
}
Map<String, dynamic> toJson() {
return this.toMap();
}
} }
///AjaxRequestAction class used by [AjaxRequest] class. ///Class used by [AjaxRequest] class.
class AjaxRequestAction { class AjaxRequestAction {
final int _value; final int _value;
const AjaxRequestAction._internal(this._value); const AjaxRequestAction._internal(this._value);
...@@ -1374,7 +1422,7 @@ class AjaxRequestAction { ...@@ -1374,7 +1422,7 @@ class AjaxRequestAction {
} }
} }
///AjaxRequestEventType class used by [AjaxRequestEvent] class. ///Class used by [AjaxRequestEvent] class.
class AjaxRequestEventType { class AjaxRequestEventType {
final String _value; final String _value;
const AjaxRequestEventType._internal(this._value); const AjaxRequestEventType._internal(this._value);
...@@ -1416,7 +1464,7 @@ class AjaxRequestEventType { ...@@ -1416,7 +1464,7 @@ class AjaxRequestEventType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AjaxRequestEvent class used by [AjaxRequest] class. It represents events measuring progress of an [AjaxRequest]. ///Class used by [AjaxRequest] class. It represents events measuring progress of an [AjaxRequest].
class AjaxRequestEvent { class AjaxRequestEvent {
///Event type. ///Event type.
AjaxRequestEventType type; AjaxRequestEventType type;
...@@ -1437,7 +1485,7 @@ class AjaxRequestEvent { ...@@ -1437,7 +1485,7 @@ class AjaxRequestEvent {
AjaxRequestEvent({this.type, this.lengthComputable, this.loaded, this.total}); AjaxRequestEvent({this.type, this.lengthComputable, this.loaded, this.total});
} }
///AjaxRequestReadyState class used by [AjaxRequest] class. It represents the state of an [AjaxRequest]. ///Class used by [AjaxRequest] class. It represents the state of an [AjaxRequest].
class AjaxRequestReadyState { class AjaxRequestReadyState {
final int _value; final int _value;
const AjaxRequestReadyState._internal(this._value); const AjaxRequestReadyState._internal(this._value);
...@@ -1486,7 +1534,7 @@ class AjaxRequestReadyState { ...@@ -1486,7 +1534,7 @@ class AjaxRequestReadyState {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///AjaxRequestHeaders class represents the HTTP headers of an [AjaxRequest]. ///Class that represents the HTTP headers of an [AjaxRequest].
class AjaxRequestHeaders { class AjaxRequestHeaders {
Map<dynamic, dynamic> _headers; Map<dynamic, dynamic> _headers;
Map<String, dynamic> _newHeaders = {}; Map<String, dynamic> _newHeaders = {};
...@@ -1511,7 +1559,7 @@ class AjaxRequestHeaders { ...@@ -1511,7 +1559,7 @@ class AjaxRequestHeaders {
} }
} }
///AjaxRequest class represents a JavaScript [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. ///Class that represents a JavaScript [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object.
class AjaxRequest { class AjaxRequest {
///Data passed as a parameter to the `XMLHttpRequest.send()` method. ///Data passed as a parameter to the `XMLHttpRequest.send()` method.
dynamic data; dynamic data;
...@@ -1629,7 +1677,7 @@ class AjaxRequest { ...@@ -1629,7 +1677,7 @@ class AjaxRequest {
} }
} }
///FetchRequestAction class used by [FetchRequest] class. ///Class used by [FetchRequest] class.
class FetchRequestAction { class FetchRequestAction {
final int _value; final int _value;
const FetchRequestAction._internal(this._value); const FetchRequestAction._internal(this._value);
...@@ -1647,7 +1695,7 @@ class FetchRequestAction { ...@@ -1647,7 +1695,7 @@ class FetchRequestAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///FetchRequestCredential class is an interface for [FetchRequestCredentialDefault], [FetchRequestFederatedCredential] and [FetchRequestPasswordCredential] classes. ///Class that is an interface for [FetchRequestCredentialDefault], [FetchRequestFederatedCredential] and [FetchRequestPasswordCredential] classes.
class FetchRequestCredential { class FetchRequestCredential {
///Type of credentials. ///Type of credentials.
String type; String type;
...@@ -1659,7 +1707,7 @@ class FetchRequestCredential { ...@@ -1659,7 +1707,7 @@ class FetchRequestCredential {
} }
} }
///FetchRequestCredentialDefault class represents the default credentials used by an [FetchRequest]. ///Class that represents the default credentials used by an [FetchRequest].
class FetchRequestCredentialDefault extends FetchRequestCredential { class FetchRequestCredentialDefault extends FetchRequestCredential {
///The value of the credentials. ///The value of the credentials.
String value; String value;
...@@ -1674,7 +1722,7 @@ class FetchRequestCredentialDefault extends FetchRequestCredential { ...@@ -1674,7 +1722,7 @@ class FetchRequestCredentialDefault extends FetchRequestCredential {
} }
} }
///FetchRequestFederatedCredential class represents a [FederatedCredential](https://developer.mozilla.org/en-US/docs/Web/API/FederatedCredential) type of credentials. ///Class that represents a [FederatedCredential](https://developer.mozilla.org/en-US/docs/Web/API/FederatedCredential) type of credentials.
class FetchRequestFederatedCredential extends FetchRequestCredential { class FetchRequestFederatedCredential extends FetchRequestCredential {
///Credential's identifier. ///Credential's identifier.
dynamic id; dynamic id;
...@@ -1707,7 +1755,7 @@ class FetchRequestFederatedCredential extends FetchRequestCredential { ...@@ -1707,7 +1755,7 @@ class FetchRequestFederatedCredential extends FetchRequestCredential {
} }
} }
///FetchRequestPasswordCredential class represents a [PasswordCredential](https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential) type of credentials. ///Class that represents a [PasswordCredential](https://developer.mozilla.org/en-US/docs/Web/API/PasswordCredential) type of credentials.
class FetchRequestPasswordCredential extends FetchRequestCredential { class FetchRequestPasswordCredential extends FetchRequestCredential {
///Credential's identifier. ///Credential's identifier.
dynamic id; dynamic id;
...@@ -1736,7 +1784,7 @@ class FetchRequestPasswordCredential extends FetchRequestCredential { ...@@ -1736,7 +1784,7 @@ class FetchRequestPasswordCredential extends FetchRequestCredential {
} }
} }
///FetchRequest class represents a HTTP request created with JavaScript using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). ///Class that represents a HTTP request created with JavaScript using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).
class FetchRequest { class FetchRequest {
///The URL of the request. ///The URL of the request.
String url; String url;
...@@ -1841,7 +1889,7 @@ class FetchRequest { ...@@ -1841,7 +1889,7 @@ class FetchRequest {
} }
} }
///ContentBlockerTriggerResourceType class represents the possible resource type defined for a [ContentBlockerTrigger]. ///Class that represents the possible resource type defined for a [ContentBlockerTrigger].
class ContentBlockerTriggerResourceType { class ContentBlockerTriggerResourceType {
final String _value; final String _value;
const ContentBlockerTriggerResourceType._internal(this._value); const ContentBlockerTriggerResourceType._internal(this._value);
...@@ -1887,7 +1935,7 @@ class ContentBlockerTriggerResourceType { ...@@ -1887,7 +1935,7 @@ class ContentBlockerTriggerResourceType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///ContentBlockerTriggerLoadType class represents the possible load type for a [ContentBlockerTrigger]. ///Class that represents the possible load type for a [ContentBlockerTrigger].
class ContentBlockerTriggerLoadType { class ContentBlockerTriggerLoadType {
final String _value; final String _value;
const ContentBlockerTriggerLoadType._internal(this._value); const ContentBlockerTriggerLoadType._internal(this._value);
...@@ -1915,7 +1963,7 @@ class ContentBlockerTriggerLoadType { ...@@ -1915,7 +1963,7 @@ class ContentBlockerTriggerLoadType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///ContentBlockerActionType class represents the kind of action that can be used with a [ContentBlockerTrigger]. ///Class that represents the kind of action that can be used with a [ContentBlockerTrigger].
class ContentBlockerActionType { class ContentBlockerActionType {
final String _value; final String _value;
const ContentBlockerActionType._internal(this._value); const ContentBlockerActionType._internal(this._value);
...@@ -1948,7 +1996,7 @@ class ContentBlockerActionType { ...@@ -1948,7 +1996,7 @@ class ContentBlockerActionType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///Cookie class represents a cookie returned by the [CookieManager]. ///Class that represents a cookie returned by the [CookieManager].
class Cookie { class Cookie {
///The name; ///The name;
String name; String name;
...@@ -1959,7 +2007,7 @@ class Cookie { ...@@ -1959,7 +2007,7 @@ class Cookie {
Cookie({@required this.name, @required this.value}); Cookie({@required this.name, @required this.value});
} }
///PermissionRequestResponseAction class used by [PermissionRequestResponse] class. ///Class used by [PermissionRequestResponse] class.
class PermissionRequestResponseAction { class PermissionRequestResponseAction {
final int _value; final int _value;
const PermissionRequestResponseAction._internal(this._value); const PermissionRequestResponseAction._internal(this._value);
...@@ -1977,7 +2025,7 @@ class PermissionRequestResponseAction { ...@@ -1977,7 +2025,7 @@ class PermissionRequestResponseAction {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///PermissionRequestResponse class represents the response used by the [androidOnPermissionRequest] event. ///Class that represents the response used by the [androidOnPermissionRequest] event.
class PermissionRequestResponse { class PermissionRequestResponse {
///Resources granted to be accessed by origin. ///Resources granted to be accessed by origin.
List<String> resources; List<String> resources;
...@@ -1994,7 +2042,7 @@ class PermissionRequestResponse { ...@@ -1994,7 +2042,7 @@ class PermissionRequestResponse {
} }
} }
///ShouldOverrideUrlLoadingAction class is used by [shouldOverrideUrlLoading] event. ///Class that is used by [shouldOverrideUrlLoading] event.
///It represents the policy to pass back to the decision handler. ///It represents the policy to pass back to the decision handler.
class ShouldOverrideUrlLoadingAction { class ShouldOverrideUrlLoadingAction {
final int _value; final int _value;
...@@ -2019,7 +2067,7 @@ class ShouldOverrideUrlLoadingAction { ...@@ -2019,7 +2067,7 @@ class ShouldOverrideUrlLoadingAction {
} }
} }
///IOSWKNavigationType class represents the type of action triggering a navigation on iOS for the [shouldOverrideUrlLoading] event. ///Class that represents the type of action triggering a navigation on iOS for the [shouldOverrideUrlLoading] event.
class IOSWKNavigationType { class IOSWKNavigationType {
final int _value; final int _value;
const IOSWKNavigationType._internal(this._value); const IOSWKNavigationType._internal(this._value);
...@@ -2054,7 +2102,7 @@ class IOSWKNavigationType { ...@@ -2054,7 +2102,7 @@ class IOSWKNavigationType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///ShouldOverrideUrlLoadingAction class represents the navigation request used by the [shouldOverrideUrlLoading] event. ///Class that represents the navigation request used by the [shouldOverrideUrlLoading] event.
class ShouldOverrideUrlLoadingRequest { class ShouldOverrideUrlLoadingRequest {
///Represents the url of the navigation request. ///Represents the url of the navigation request.
String url; String url;
...@@ -2085,7 +2133,7 @@ class ShouldOverrideUrlLoadingRequest { ...@@ -2085,7 +2133,7 @@ class ShouldOverrideUrlLoadingRequest {
ShouldOverrideUrlLoadingRequest({this.url, this.method, this.headers, this.isForMainFrame, this.androidHasGesture, this.androidIsRedirect, this.iosWKNavigationType}); ShouldOverrideUrlLoadingRequest({this.url, this.method, this.headers, this.isForMainFrame, this.androidHasGesture, this.androidIsRedirect, this.iosWKNavigationType});
} }
///OnCreateWindowRequest class represents the navigation request used by the [shouldOverrideUrlLoading] event. ///Class that represents the navigation request used by the [shouldOverrideUrlLoading] event.
class OnCreateWindowRequest { class OnCreateWindowRequest {
///Represents the url of the navigation request. ///Represents the url of the navigation request.
String url; String url;
...@@ -2102,7 +2150,7 @@ class OnCreateWindowRequest { ...@@ -2102,7 +2150,7 @@ class OnCreateWindowRequest {
OnCreateWindowRequest({this.url, this.androidIsDialog, this.androidIsUserGesture, this.iosWKNavigationType}); OnCreateWindowRequest({this.url, this.androidIsDialog, this.androidIsUserGesture, this.iosWKNavigationType});
} }
///AndroidWebStorage class encapsulates information about the amount of storage currently used by an origin for the JavaScript storage APIs. ///Class that encapsulates information about the amount of storage currently used by an origin for the JavaScript storage APIs.
///An origin comprises the host, scheme and port of a URI. See [AndroidWebStorageManager] for details. ///An origin comprises the host, scheme and port of a URI. See [AndroidWebStorageManager] for details.
class AndroidWebStorageOrigin { class AndroidWebStorageOrigin {
///The string representation of this origin. ///The string representation of this origin.
...@@ -2129,7 +2177,7 @@ class AndroidWebStorageOrigin { ...@@ -2129,7 +2177,7 @@ class AndroidWebStorageOrigin {
} }
} }
///IOSWKWebsiteDataType class represents a website data type. ///Class that represents a website data type.
/// ///
///**NOTE**: available on iOS 9.0+. ///**NOTE**: available on iOS 9.0+.
class IOSWKWebsiteDataType { class IOSWKWebsiteDataType {
...@@ -2220,7 +2268,7 @@ class IOSWKWebsiteDataType { ...@@ -2220,7 +2268,7 @@ class IOSWKWebsiteDataType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///WKWebsiteDataRecord class represents website data, grouped by domain name using the public suffix list. ///Class that represents website data, grouped by domain name using the public suffix list.
/// ///
///**NOTE**: available on iOS 9.0+. ///**NOTE**: available on iOS 9.0+.
class IOSWKWebsiteDataRecord { class IOSWKWebsiteDataRecord {
...@@ -2306,7 +2354,7 @@ class LongPressHitTestResultType { ...@@ -2306,7 +2354,7 @@ class LongPressHitTestResultType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///LongPressHitTestResult class represents the hit result for hitting an HTML elements. Used by [onLongPressHitTestResult] event. ///Class that represents the hit result for hitting an HTML elements. Used by [onLongPressHitTestResult] event.
class LongPressHitTestResult { class LongPressHitTestResult {
///The type of the hit test result. ///The type of the hit test result.
LongPressHitTestResultType type; LongPressHitTestResultType type;
......
...@@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; ...@@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
import 'types.dart'; import 'types.dart';
///WebStorageManager class implements a singleton object (shared instance) which manages the web storage used by WebView instances. ///Class that implements a singleton object (shared instance) which manages the web storage used by WebView instances.
/// ///
///**NOTE for iOS**: available from iOS 9.0+. ///**NOTE for iOS**: available from iOS 9.0+.
class WebStorageManager { class WebStorageManager {
...@@ -30,7 +30,7 @@ class WebStorageManager { ...@@ -30,7 +30,7 @@ class WebStorageManager {
static Future<dynamic> _handleMethod(MethodCall call) async {} static Future<dynamic> _handleMethod(MethodCall call) async {}
} }
///AndroidWebStorageManager class is used to manage the JavaScript storage APIs provided by the WebView. ///Class that is used to manage the JavaScript storage APIs provided by the WebView.
///It manages the Application Cache API, the Web SQL Database API and the HTML5 Web Storage API. ///It manages the Application Cache API, the Web SQL Database API and the HTML5 Web Storage API.
class AndroidWebStorageManager { class AndroidWebStorageManager {
///Gets the origins currently using either the Application Cache or Web SQL Database APIs. ///Gets the origins currently using either the Application Cache or Web SQL Database APIs.
...@@ -83,7 +83,7 @@ class AndroidWebStorageManager { ...@@ -83,7 +83,7 @@ class AndroidWebStorageManager {
} }
} }
///IOSWebStorageManager class represents various types of data that a website might make use of. ///Class that represents various types of data that a website might make use of.
///This includes cookies, disk and memory caches, and persistent data such as WebSQL, IndexedDB databases, and local storage. ///This includes cookies, disk and memory caches, and persistent data such as WebSQL, IndexedDB databases, and local storage.
/// ///
///**NOTE**: available on iOS 9.0+. ///**NOTE**: available on iOS 9.0+.
......
import 'types.dart';
import 'in_app_webview_controller.dart';
///Abstract class that represents a WebView. Used by [WebView] and [HeadlessInAppWebView].
abstract class WebView {
///Event fired when the [WebView] is created.
final void Function(InAppWebViewController controller) onWebViewCreated;
///Event fired when the [WebView] starts to load an [url].
final void Function(InAppWebViewController controller, String url)
onLoadStart;
///Event fired when the [WebView] finishes loading an [url].
final void Function(InAppWebViewController controller, String url) onLoadStop;
///Event fired when the [WebView] encounters an error loading an [url].
final void Function(InAppWebViewController controller, String url, int code,
String message) onLoadError;
///Event fired when the [WebView] main page receives an HTTP error.
///
///[url] represents the url of the main page that received the HTTP error.
///
///[statusCode] represents the status code of the response. HTTP errors have status codes >= 400.
///
///[description] represents the description of the HTTP error. On iOS, it is always an empty string.
///
///**NOTE**: available on Android 23+.
final void Function(InAppWebViewController controller, String url,
int statusCode, String description) onLoadHttpError;
///Event fired when the current [progress] of loading a page is changed.
final void Function(InAppWebViewController controller, int progress)
onProgressChanged;
///Event fired when the [WebView] receives a [ConsoleMessage].
final void Function(
InAppWebViewController controller, ConsoleMessage consoleMessage)
onConsoleMessage;
///Give the host application a chance to take control when a URL is about to be loaded in the current WebView. This event is not called on the initial load of the WebView.
///
///Note that on Android 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 by default.
///However, if you want to cancel requests for subframes, you can use the [AndroidInAppWebViewOptions.regexToCancelSubFramesLoading] option
///to write a Regular Expression that, if the url request of a subframe matches, then the request of that subframe is canceled.
///
///Also, on Android, this method is not called for POST requests.
///
///[shouldOverrideUrlLoadingRequest] represents the navigation request.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldOverrideUrlLoading] option to `true`.
final Future<ShouldOverrideUrlLoadingAction> Function(
InAppWebViewController controller,
ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest)
shouldOverrideUrlLoading;
///Event fired when the [WebView] loads a resource.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnLoadResource] and [InAppWebViewOptions.javaScriptEnabled] options to `true`.
final void Function(
InAppWebViewController controller, LoadedResource resource)
onLoadResource;
///Event fired when the [WebView] scrolls.
///
///[x] represents the current horizontal scroll origin in pixels.
///
///[y] represents the current vertical scroll origin in pixels.
final void Function(InAppWebViewController controller, int x, int y)
onScrollChanged;
///Event fired when [WebView] recognizes and starts a downloadable file.
///
///[url] represents the url of the file.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnDownloadStart] option to `true`.
final void Function(InAppWebViewController controller, String url)
onDownloadStart;
///Event fired when the [WebView] finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`.
///
///[scheme] represents the scheme of the url.
///
///[url] represents the url of the request.
final Future<CustomSchemeResponse> Function(
InAppWebViewController controller, String scheme, String url)
onLoadResourceCustomScheme;
///Event fired when the [WebView] requests the host application to create a new window,
///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side.
///
///[onCreateWindowRequest] represents the request.
///
///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`.
final void Function(InAppWebViewController controller,
OnCreateWindowRequest onCreateWindowRequest) onCreateWindow;
///Event fired when javascript calls the `alert()` method to display an alert dialog.
///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final Future<JsAlertResponse> Function(
InAppWebViewController controller, String message) onJsAlert;
///Event fired when javascript calls the `confirm()` method to display a confirm dialog.
///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final Future<JsConfirmResponse> Function(
InAppWebViewController controller, String message) onJsConfirm;
///Event fired when javascript calls the `prompt()` method to display a prompt dialog.
///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
///
///[defaultValue] represents the default value displayed in the prompt dialog.
final Future<JsPromptResponse> Function(InAppWebViewController controller,
String message, String defaultValue) onJsPrompt;
///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request.
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge].
final Future<HttpAuthResponse> Function(
InAppWebViewController controller, HttpAuthChallenge challenge)
onReceivedHttpAuthRequest;
///Event fired when the WebView need to perform server trust authentication (certificate validation).
///The host application must return either [ServerTrustAuthResponse] instance with [ServerTrustAuthResponseAction.CANCEL] or [ServerTrustAuthResponseAction.PROCEED].
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge].
final Future<ServerTrustAuthResponse> Function(
InAppWebViewController controller, ServerTrustChallenge challenge)
onReceivedServerTrustAuthRequest;
///Notify the host application to handle an SSL client certificate request.
///Webview stores the response in memory (for the life of the application) if [ClientCertResponseAction.PROCEED] or [ClientCertResponseAction.CANCEL]
///is called and does not call [onReceivedClientCertRequest] again for the same host and port pair.
///Note that, multiple layers in chromium network stack might be caching the responses.
///
///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ClientCertChallenge].
final Future<ClientCertResponse> Function(
InAppWebViewController controller, ClientCertChallenge challenge)
onReceivedClientCertRequest;
///Event fired as find-on-page operations progress.
///The listener may be notified multiple times while the operation is underway, and the numberOfMatches value should not be considered final unless [isDoneCounting] is true.
///
///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
///
///[numberOfMatches] represents how many matches have been found.
///
///[isDoneCounting] whether the find operation has actually completed.
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) onFindResultReceived;
///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.
///
///[ajaxRequest] represents the `XMLHttpRequest`.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequest> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
shouldInterceptAjaxRequest;
///Event fired whenever the `readyState` attribute of an `XMLHttpRequest` changes.
///It gives the host application a chance to abort the request.
///
///[ajaxRequest] represents the [XMLHttpRequest].
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxReadyStateChange;
///Event fired as an `XMLHttpRequest` progress.
///It gives the host application a chance to abort the request.
///
///[ajaxRequest] represents the [XMLHttpRequest].
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptAjaxRequest] option 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
///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.
final Future<AjaxRequestAction> Function(
InAppWebViewController controller, AjaxRequest ajaxRequest)
onAjaxProgress;
///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.
///
///[fetchRequest] represents a resource request.
///
///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldInterceptFetchRequest] option 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
///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.
final Future<FetchRequest> Function(
InAppWebViewController controller, FetchRequest fetchRequest)
shouldInterceptFetchRequest;
///Event fired when the host application updates its visited links database.
///This event is also fired when the navigation state of the [WebView] changes through the usage of
///javascript **[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)** functions (`pushState()`, `replaceState()`) and `onpopstate` event
///or, also, when the javascript `window.location` changes without reloading the webview (for example appending or modifying an hash to the url).
///
///[url] represents the url being visited.
///
///[androidIsReload] indicates if this url is being reloaded. Available only on Android.
final void Function(
InAppWebViewController controller, String url, bool androidIsReload)
onUpdateVisitedHistory;
///Event fired when `window.print()` is called from JavaScript side.
///
///[url] represents the url on which is called.
///
///**NOTE**: available on Android 21+.
final void Function(InAppWebViewController controller, String url) onPrint;
///Event fired when an HTML element of the webview has been clicked and held.
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
///
///[url] represents the url of the request.
///
///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat].
///
///**NOTE**: available only on Android 27+.
final Future<SafeBrowsingResponse> Function(InAppWebViewController controller,
String url, SafeBrowsingThreat threatType) androidOnSafeBrowsingHit;
///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied.
///
///[origin] represents the origin of the web page which is trying to access the restricted resources.
///
///[resources] represents the array of resources the web content wants to access.
///
///**NOTE**: available only on Android 23+.
final Future<PermissionRequestResponse> Function(
InAppWebViewController controller,
String origin,
List<String> resources) androidOnPermissionRequest;
///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin.
///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https.
///On non-secure origins geolocation requests are automatically denied.
///
///[origin] represents the origin of the web content attempting to use the Geolocation API.
///
///**NOTE**: available only on Android.
final Future<GeolocationPermissionShowPromptResponse> Function(
InAppWebViewController controller, String origin)
androidOnGeolocationPermissionsShowPrompt;
///Notify the host application that a request for Geolocation permissions, made with a previous call to [androidOnGeolocationPermissionsShowPrompt] has been canceled.
///Any related UI should therefore be hidden.
///
///**NOTE**: available only on Android.
final Future<void> Function(InAppWebViewController controller)
androidOnGeolocationPermissionsHidePrompt;
///Invoked when the web view's web content process is terminated.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller)
iosOnWebContentProcessDidTerminate;
///Called when the web view begins to receive web content.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
///Called when a web view receives a server redirect.
///
///**NOTE**: available only on iOS.
final Future<void> Function(InAppWebViewController controller)
iosOnDidReceiveServerRedirectForProvisionalNavigation;
///Initial url that will be loaded.
final String initialUrl;
///Initial asset file that will be loaded. See [InAppWebViewController.loadFile] for explanation.
final String initialFile;
///Initial [InAppWebViewInitialData] that will be loaded.
final InAppWebViewInitialData initialData;
///Initial headers that will be used.
final Map<String, String> initialHeaders;
///Initial options that will be used.
final InAppWebViewGroupOptions initialOptions;
WebView(
{this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions});
}
\ No newline at end of file
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview or open an in-app browser window. description: A Flutter plugin that allows you to add an inline webview or open an in-app browser window.
version: 3.0.0 version: 3.1.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: environment:
......
...@@ -4,6 +4,7 @@ export NODE_SERVER_IP=$1 ...@@ -4,6 +4,7 @@ export NODE_SERVER_IP=$1
dart tool/env.dart dart tool/env.dart
cd nodejs_server_test_auth_basic_and_ssl cd nodejs_server_test_auth_basic_and_ssl
node index.js & node index.js &
flutter clean
cd ../example cd ../example
flutter clean flutter clean
flutter driver -t test_driver/app.dart flutter driver -t test_driver/app.dart
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment