diff --git a/.idea/markdown-navigator.xml b/.idea/markdown-navigator.xml new file mode 100644 index 00000000..3e62462d --- /dev/null +++ b/.idea/markdown-navigator.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml new file mode 100644 index 00000000..57927c5a --- /dev/null +++ b/.idea/markdown-navigator/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index cc95c879..6e99f7c7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,21 +15,19 @@ - - - - + - - + + + + - @@ -45,39 +43,65 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + + + + - + - - + + @@ -157,21 +181,21 @@ - @@ -179,6 +203,8 @@ + + @@ -191,6 +217,11 @@ + + + + + @@ -201,8 +232,6 @@ - - @@ -369,33 +398,33 @@ - + - - + - + - - + + + @@ -410,6 +439,7 @@ + @@ -421,22 +451,21 @@ - + - + - + - @@ -450,13 +479,6 @@ - - - - - - - @@ -512,11 +534,6 @@ - - - - - @@ -591,26 +608,12 @@ - - - - - - - - - - - - - - @@ -634,18 +637,6 @@ - - - - - - - - - - - - @@ -679,35 +670,91 @@ - + - + - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + diff --git a/README.md b/README.md index fdf12b70..94b1e01b 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,6 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser. - __presentationStyle__: Set the custom modal presentation style when presenting the WebView. The default value is `0 //fullscreen`. See [UIModalPresentationStyle](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle) for all the available styles. - __transitionStyle__: Set to the custom transition style when presenting the WebView. The default value is `0 //crossDissolve`. See [UIModalTransitionStyle](https://developer.apple.com/documentation/uikit/uimodaltransitionStyle) for all the available styles. - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`. - - __keyboardDisplayRequiresUserAction__: Set to `true` if you want the user must explicitly tap the elements in the WebView to display the keyboard (or other relevant input view) for that element. When set to `false`, a focus event on an element causes the input view to be displayed and associated with that element automatically. The default value is `true`. - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`. - __allowsAirPlayForMediaPlayback__: Set to `true` to allow AirPlay. The default value is `true`. - __allowsBackForwardNavigationGestures__: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. diff --git a/android/build.gradle b/android/build.gradle index 28a1e933..29bac20f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -38,4 +38,5 @@ android { dependencies { implementation 'com.android.support:customtabs:27.1.1' implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.squareup.okhttp3:mockwebserver:3.11.0' } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java index 68988eee..b09e5b91 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java @@ -1,29 +1,97 @@ package com.pichillilorenzo.flutter_inappbrowser; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; import android.os.Build; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.text.TextUtils; import android.util.Log; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.HttpAuthHandler; +import android.webkit.MimeTypeMap; import android.webkit.SslErrorHandler; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; +import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; public class InAppBrowserWebViewClient extends WebViewClient { protected static final String LOG_TAG = "IABWebViewClient"; private WebViewActivity activity; + Map statusCodeMapping = new HashMap(); public InAppBrowserWebViewClient(WebViewActivity activity) { super(); this.activity = activity; + statusCodeMapping.put(100, "Continue"); + statusCodeMapping.put(101, "Switching Protocols"); + statusCodeMapping.put(200, "OK"); + statusCodeMapping.put(201, "Created"); + statusCodeMapping.put(202, "Accepted"); + statusCodeMapping.put(203, "Non-Authoritative Information"); + statusCodeMapping.put(204, "No Content"); + statusCodeMapping.put(205, "Reset Content"); + statusCodeMapping.put(206, "Partial Content"); + statusCodeMapping.put(300, "Multiple Choices"); + statusCodeMapping.put(301, "Moved Permanently"); + statusCodeMapping.put(302, "Found"); + statusCodeMapping.put(303, "See Other"); + statusCodeMapping.put(304, "Not Modified"); + statusCodeMapping.put(307, "Temporary Redirect"); + statusCodeMapping.put(308, "Permanent Redirect"); + statusCodeMapping.put(400, "Bad Request"); + statusCodeMapping.put(401, "Unauthorized"); + statusCodeMapping.put(403, "Forbidden"); + statusCodeMapping.put(404, "Not Found"); + statusCodeMapping.put(405, "Method Not Allowed"); + statusCodeMapping.put(406, "Not Acceptable"); + statusCodeMapping.put(407, "Proxy Authentication Required"); + statusCodeMapping.put(408, "Request Timeout"); + statusCodeMapping.put(409, "Conflict"); + statusCodeMapping.put(410, "Gone"); + statusCodeMapping.put(411, "Length Required"); + statusCodeMapping.put(412, "Precondition Failed"); + statusCodeMapping.put(413, "Payload Too Large"); + statusCodeMapping.put(414, "URI Too Long"); + statusCodeMapping.put(415, "Unsupported Media Type"); + statusCodeMapping.put(416, "Range Not Satisfiable"); + statusCodeMapping.put(417, "Expectation Failed"); + statusCodeMapping.put(418, "I'm a teapot"); + statusCodeMapping.put(422, "Unprocessable Entity"); + statusCodeMapping.put(425, "Too Early"); + statusCodeMapping.put(426, "Upgrade Required"); + statusCodeMapping.put(428, "Precondition Required"); + statusCodeMapping.put(429, "Too Many Requests"); + statusCodeMapping.put(431, "Request Header Fields Too Large"); + statusCodeMapping.put(451, "Unavailable For Legal Reasons"); + statusCodeMapping.put(500, "Internal Server Error"); + statusCodeMapping.put(501, "Not Implemented"); + statusCodeMapping.put(502, "Bad Gateway"); + statusCodeMapping.put(503, "Service Unavailable"); + statusCodeMapping.put(504, "Gateway Timeout"); + statusCodeMapping.put(505, "HTTP Version Not Supported"); + statusCodeMapping.put(511, "Network Authentication Required"); } @Override @@ -202,4 +270,53 @@ public class InAppBrowserWebViewClient extends WebViewClient { super.onReceivedHttpAuthRequest(view, handler, host, realm); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){ + final String url = request.getUrl().toString(); + + Request mRequest = new Request.Builder().url(url).build(); + + try { + Response response = activity.httpClient.newCall(mRequest).execute(); + String reasonPhrase = response.message(); + if (reasonPhrase.equals("")) { + reasonPhrase = statusCodeMapping.get(response.code()); + Log.d(LOG_TAG, reasonPhrase); + } + reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase; + + Map headers = new HashMap(); + for (Map.Entry> entry : response.headers().toMultimap().entrySet()) { + String value = ""; + for (String val: entry.getValue()) { + value += (value == "") ? val : "; " + val; + } + headers.put(entry.getKey().toLowerCase(), value); + } + + Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); + obj.put("url", url); + obj.put("statusCode", response.code()); + obj.put("headers", headers); + + InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadResource", obj); + + return new WebResourceResponse( + response.header("content-type", "text/plain").split(";")[0].trim(), + response.header("content-encoding"), + response.code(), + reasonPhrase, + headers, + response.body().byteStream() + ); + } catch (IOException e) { + e.printStackTrace(); + Log.d(LOG_TAG, e.getMessage()); + } + return null; + } + + } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java index 47fc9b66..cafbaabd 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import io.flutter.plugin.common.MethodChannel; +import okhttp3.OkHttpClient; public class WebViewActivity extends AppCompatActivity { @@ -37,6 +38,7 @@ public class WebViewActivity extends AppCompatActivity { ProgressBar progressBar; public boolean isLoading = false; public boolean isHidden = false; + OkHttpClient httpClient; static final String jsConsoleLogScript = "(function() {\n"+ " var oldLogs = {\n"+ @@ -87,6 +89,8 @@ public class WebViewActivity extends AppCompatActivity { prepareWebView(); + httpClient = new OkHttpClient(); + webView.loadUrl(url, headers); } diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6da50500..517e7413 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -176,7 +175,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -211,7 +210,6 @@ files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, ED5EF13121506A3E0065FD45 /* WebView.storyboard in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, @@ -336,12 +334,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -390,12 +390,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f5a8db1a..ba1a9822 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - + - - - - + + @@ -15,6 +13,10 @@ + + + + @@ -22,14 +24,10 @@ - - - - - - - - + + + + @@ -43,7 +41,7 @@ - + @@ -68,29 +66,28 @@ - - - + + + - - + + - @@ -102,7 +99,7 @@ - + diff --git a/example/lib/main.dart b/example/lib/main.dart index 7c1709e5..317f294f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -71,6 +71,13 @@ class MyInAppBrowser extends InAppBrowser { this.loadUrl(url); } + @override + void onLoadResource(String url, int statusCode, Map headers) { + print("\n\n resource: $url\n\n"); + print(statusCode); + print(headers); + } + @override void onConsoleMessage(ConsoleMessage consoleMessage) { print(""" diff --git a/ios/Classes/InAppBrowserOptions.swift b/ios/Classes/InAppBrowserOptions.swift index b7f38b5b..252e7376 100644 --- a/ios/Classes/InAppBrowserOptions.swift +++ b/ios/Classes/InAppBrowserOptions.swift @@ -30,7 +30,7 @@ public class InAppBrowserOptions: Options { var presentationStyle = 0 //fullscreen var transitionStyle = 0 //crossDissolve var enableViewportScale = false - var keyboardDisplayRequiresUserAction = true + //var keyboardDisplayRequiresUserAction = true var suppressesIncrementalRendering = false var allowsAirPlayForMediaPlayback = true var allowsBackForwardNavigationGestures = true diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index 80e0ded6..0463b4a3 100644 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -92,8 +92,16 @@ extension WKWebView{ } -class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler { - @IBOutlet var webView: WKWebView! +class WKWebView_IBWrapper: WKWebView { + required convenience init?(coder: NSCoder) { + let config = WKWebViewConfiguration() + self.init(frame: .zero, configuration: config) + self.translatesAutoresizingMaskIntoConstraints = false + } +} + +class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler, MyURLProtocolDelegate { + @IBOutlet var webView: WKWebView_IBWrapper! @IBOutlet var closeButton: UIButton! @IBOutlet var reloadButton: UIBarButtonItem! @IBOutlet var backButton: UIBarButtonItem! @@ -122,6 +130,15 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio } override func viewDidLoad() { + super.viewDidLoad() + + URLProtocol.wk_registerScheme("http") + URLProtocol.wk_registerScheme("https") + + MyURLProtocol.URLProtocolDelegate = self + + URLProtocol.registerClass(MyURLProtocol.self) + webView.uiDelegate = self webView.navigationDelegate = self @@ -237,7 +254,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio let jscriptWebkitTouchCallout = WKUserScript(source: "document.body.style.webkitTouchCallout='none';", injectionTime: .atDocumentEnd, forMainFrameOnly: true) self.webView.configuration.userContentController.addUserScript(jscriptWebkitTouchCallout) - let jsConsoleLogScript = WKUserScript(source: jsConsoleLog, injectionTime: .atDocumentEnd, forMainFrameOnly: true) + let jsConsoleLogScript = WKUserScript(source: jsConsoleLog, injectionTime: .atDocumentStart, forMainFrameOnly: false) self.webView.configuration.userContentController.addUserScript(jsConsoleLogScript) self.webView.configuration.userContentController.add(self, name: "consoleLog") self.webView.configuration.userContentController.add(self, name: "consoleDebug") @@ -259,7 +276,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio self.webView.configuration.allowsInlineMediaPlayback = (browserOptions?.allowsInlineMediaPlayback)! - self.webView.keyboardDisplayRequiresUserAction = browserOptions?.keyboardDisplayRequiresUserAction + + //self.webView.keyboardDisplayRequiresUserAction = browserOptions?.keyboardDisplayRequiresUserAction + self.webView.configuration.suppressesIncrementalRendering = (browserOptions?.suppressesIncrementalRendering)! self.webView.allowsBackForwardNavigationGestures = (browserOptions?.allowsBackForwardNavigationGestures)! if #available(iOS 9.0, *) { @@ -469,6 +488,16 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio decisionHandler(.allow) } + func webView(_ webView: WKWebView, + decidePolicyFor navigationResponse: WKNavigationResponse, + decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + + //dump((navigationResponse.response as! HTTPURLResponse)) + //print(navigationResponse.response.mimeType) + //print(navigationResponse.response.url) + decisionHandler(.allow) + } + // func webView(_ webView: WKWebView, // decidePolicyFor navigationResponse: WKNavigationResponse, // decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { @@ -518,9 +547,6 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // update url, stop spinner, update back/forward - // evaluate the console log script - webView.evaluateJavaScript(jsConsoleLog) - currentURL = webView.url updateUrlTextField(url: (currentURL?.absoluteString)!) backButton.isEnabled = webView.canGoBack @@ -529,15 +555,11 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio navigationDelegate?.onLoadStop(uuid: self.uuid, webView: webView) } -// func webView(_ webView: WKWebView, -// didFailProvisionalNavigation navigation: WKNavigation!, -// withError error: Error) { -// print("webView:didFailProvisionalNavigationWithError - \(Int(error._code)): \(error.localizedDescription)") -// backButton.isEnabled = webView.canGoBack -// forwardButton.isEnabled = webView.canGoForward -// spinner.stopAnimating() -// navigationDelegate?.webView(uuid: self.uuid, webView: webView, didFailLoadWithError: error) -// } + func webView(_ view: WKWebView, + didFailProvisionalNavigation navigation: WKNavigation!, + withError error: Error) { + webView(view, didFail: navigation, withError: error) + } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print("webView:didFailNavigationWithError - \(Int(error._code)): \(error.localizedDescription)") @@ -547,8 +569,11 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error) } + func didReceiveResponse(_ response: URLResponse, from request: URLRequest?) { + navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response) + } + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - print(message.name) if message.name.starts(with: "console") { var messageLevel = "LOG" switch (message.name) { diff --git a/ios/Classes/MyURLProtocol.swift b/ios/Classes/MyURLProtocol.swift new file mode 100644 index 00000000..f38095c1 --- /dev/null +++ b/ios/Classes/MyURLProtocol.swift @@ -0,0 +1,102 @@ +// +// MyURLProtocol.swift +// Pods +// +// Created by Lorenzo on 12/10/18. +// + +import Foundation + +class MyURLProtocol: URLProtocol { + + struct Constants { + static let RequestHandledKey = "URLProtocolRequestHandled" + } + + var session: URLSession? + var sessionTask: URLSessionDataTask? + static var URLProtocolDelegate: MyURLProtocolDelegate? + + override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { + super.init(request: request, cachedResponse: cachedResponse, client: client) + + if session == nil { + session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } + } + + override class func canInit(with request: URLRequest) -> Bool { + if MyURLProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil { + return false + } + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)! + MyURLProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest) + sessionTask = session?.dataTask(with: newRequest as URLRequest) + sessionTask?.resume() + } + + override func stopLoading() { + sessionTask?.cancel() + } +} + +extension MyURLProtocol: URLSessionDataDelegate { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + client?.urlProtocol(self, didLoad: data) + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) + MyURLProtocol.URLProtocolDelegate?.didReceiveResponse(response, from: dataTask.currentRequest) + completionHandler(.allow) + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error = error { + client?.urlProtocol(self, didFailWithError: error) + } else { + client?.urlProtocolDidFinishLoading(self) + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) + completionHandler(request) + } + + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + guard let error = error else { return } + client?.urlProtocol(self, didFailWithError: error) + } + + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + let protectionSpace = challenge.protectionSpace + let sender = challenge.sender + + if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + if let serverTrust = protectionSpace.serverTrust { + let credential = URLCredential(trust: serverTrust) + sender?.use(credential, for: challenge) + completionHandler(.useCredential, credential) + return + } + } + } + + func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + client?.urlProtocolDidFinishLoading(self) + } +} + +protocol MyURLProtocolDelegate { + func didReceiveResponse(_ response: URLResponse, from request: URLRequest?) +} diff --git a/ios/Classes/NSURLProtocol+WKWebVIew.h b/ios/Classes/NSURLProtocol+WKWebVIew.h new file mode 100644 index 00000000..d0dd71f3 --- /dev/null +++ b/ios/Classes/NSURLProtocol+WKWebVIew.h @@ -0,0 +1,23 @@ +// +// NSURLProtocol+WKWebVIew.h +// Pods +// +// Created by Lorenzo on 11/10/18. +// + +#ifndef NSURLProtocol_WKWebVIew_h +#define NSURLProtocol_WKWebVIew_h + + +#endif /* NSURLProtocol_WKWebVIew_h */ + +#import + +@interface NSURLProtocol (WKWebVIew) + ++ (void)wk_registerScheme:(NSString*)scheme; + ++ (void)wk_unregisterScheme:(NSString*)scheme; + + +@end diff --git a/ios/Classes/NSURLProtocol+WKWebVIew.m b/ios/Classes/NSURLProtocol+WKWebVIew.m new file mode 100644 index 00000000..176ded26 --- /dev/null +++ b/ios/Classes/NSURLProtocol+WKWebVIew.m @@ -0,0 +1,54 @@ +// +// NSURLProtocol+WKWebVIew.m +// Pods +// +// Created by Lorenzo on 11/10/18. +// + +#import +#import "NSURLProtocol+WKWebVIew.h" +#import +//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。 +FOUNDATION_STATIC_INLINE Class ContextControllerClass() { + static Class cls; + if (!cls) { + cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class]; + } + return cls; +} + +FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() { + return NSSelectorFromString(@"registerSchemeForCustomProtocol:"); +} + +FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() { + return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:"); +} + +@implementation NSURLProtocol (WebKitSupport) + ++ (void)wk_registerScheme:(NSString *)scheme { + Class cls = ContextControllerClass(); + SEL sel = RegisterSchemeSelector(); + if ([(id)cls respondsToSelector:sel]) { + // 放弃编辑器警告 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [(id)cls performSelector:sel withObject:scheme]; +#pragma clang diagnostic pop + } +} + ++ (void)wk_unregisterScheme:(NSString *)scheme { + Class cls = ContextControllerClass(); + SEL sel = UnregisterSchemeSelector(); + if ([(id)cls respondsToSelector:sel]) { + // 放弃编辑器警告 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [(id)cls performSelector:sel withObject:scheme]; +#pragma clang diagnostic pop + } +} + +@end diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 9f8e3fd4..5917082a 100644 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -25,6 +25,14 @@ import SafariServices let WEBVIEW_STORYBOARD = "WebView" let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" +extension Dictionary where Key: ExpressibleByStringLiteral { + public mutating func lowercaseKeys() { + for key in self.keys { + self[String(describing: key).lowercased() as! Key] = self.removeValue(forKey: key) + } + } +} + public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] @@ -481,6 +489,21 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } + func onLoadResource(uuid: String, webView: WKWebView, response: URLResponse) { + if self.webViewControllers[uuid] != nil { + var headers = (response as! HTTPURLResponse).allHeaderFields as! [String: String] + headers.lowercaseKeys() + + let arguments: [String : Any] = [ + "uuid": uuid, + "url": response.url?.absoluteString ?? "", + "statusCode": (response as! HTTPURLResponse).statusCode, + "headers": headers + ] + channel.invokeMethod("onLoadResource", arguments: arguments) + } + } + func onExit(uuid: String) { channel.invokeMethod("onExit", arguments: ["uuid": uuid]) } diff --git a/lib/flutter_inappbrowser.dart b/lib/flutter_inappbrowser.dart index 832dee6a..5348cffc 100644 --- a/lib/flutter_inappbrowser.dart +++ b/lib/flutter_inappbrowser.dart @@ -106,6 +106,12 @@ class InAppBrowser { String url = call.arguments["url"]; shouldOverrideUrlLoading(url); break; + case "onLoadResource": + String url = call.arguments["url"]; + int statusCode = call.arguments["statusCode"]; + Map headers = call.arguments["headers"]; + onLoadResource(url, statusCode, headers.cast()); + break; case "onConsoleMessage": String sourceURL = call.arguments["sourceURL"]; int lineNumber = call.arguments["lineNumber"]; @@ -173,7 +179,6 @@ class InAppBrowser { /// - __presentationStyle__: Set the custom modal presentation style when presenting the WebView. The default value is `0 //fullscreen`. See [UIModalPresentationStyle](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle) for all the available styles. /// - __transitionStyle__: Set to the custom transition style when presenting the WebView. The default value is `0 //crossDissolve`. See [UIModalTransitionStyle](https://developer.apple.com/documentation/uikit/uimodaltransitionStyle) for all the available styles. /// - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`. - /// - __keyboardDisplayRequiresUserAction__: Set to `true` if you want the user must explicitly tap the elements in the WebView to display the keyboard (or other relevant input view) for that element. When set to `false`, a focus event on an element causes the input view to be displayed and associated with that element automatically. The default value is `true`. /// - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`. /// - __allowsAirPlayForMediaPlayback__: Set to `true` to allow AirPlay. The default value is `true`. /// - __allowsBackForwardNavigationGestures__: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. @@ -323,6 +328,11 @@ class InAppBrowser { } + ///Event fires when the [InAppBrowser] webview will load the resource specified by the given [url]. + void onLoadResource(String url, int statusCode, Map headers) { + + } + ///Event fires when the [InAppBrowser] webview receives a [ConsoleMessage]. void onConsoleMessage(ConsoleMessage consoleMessage) { @@ -392,7 +402,6 @@ class ChromeSafariBrowser { Future open(String url, {Map options = const {}, Map headersFallback = const {}, Map optionsFallback = const {}}) async { Map args = {}; args.putIfAbsent('uuid', () => uuid); - print(browserFallback.uuid); args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : ''); args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headersFallback);