Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

250 rindas
8.0 KiB

  1. //
  2. // WebViewLayoutController.swift
  3. // webview_universal
  4. //
  5. // Created by Bin Yang on 2021/11/18.
  6. //
  7. import Cocoa
  8. import FlutterMacOS
  9. import WebKit
  10. class WebViewLayoutController: NSViewController {
  11. private lazy var titleBarController: FlutterViewController = {
  12. let project = FlutterDartProject()
  13. project.dartEntrypointArguments = ["web_view_title_bar", "\(viewId)", "\(titleBarTopPadding)"]
  14. return FlutterViewController(project: project)
  15. }()
  16. private lazy var webView: WKWebView = {
  17. WKWebView()
  18. }()
  19. private var javaScriptHandlerNames: [String] = []
  20. weak var webViewPlugin: WebviewUniversalPlugin?
  21. private var defaultUserAgent: String?
  22. private let methodChannel: FlutterMethodChannel
  23. private let viewId: Int64
  24. private let titleBarHeight: Int
  25. private let titleBarTopPadding: Int
  26. public init(methodChannel: FlutterMethodChannel, viewId: Int64, titleBarHeight: Int, titleBarTopPadding: Int) {
  27. self.viewId = viewId
  28. self.methodChannel = methodChannel
  29. self.titleBarHeight = titleBarHeight
  30. self.titleBarTopPadding = titleBarTopPadding
  31. super.init(nibName: "WebViewLayoutController", bundle: Bundle(for: WebViewLayoutController.self))
  32. }
  33. required init?(coder: NSCoder) {
  34. fatalError("init(coder:) has not been implemented")
  35. }
  36. override func loadView() {
  37. super.loadView()
  38. addChild(titleBarController)
  39. titleBarController.view.translatesAutoresizingMaskIntoConstraints = false
  40. // Register titlebar plugins
  41. ClientMessageChannelPlugin.register(with: titleBarController.registrar(forPlugin: "WebviewUniversalPlugin"))
  42. let flutterView = titleBarController.view
  43. flutterView.translatesAutoresizingMaskIntoConstraints = false
  44. view.addSubview(flutterView)
  45. let constraints = [
  46. flutterView.topAnchor.constraint(equalTo: view.topAnchor),
  47. flutterView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  48. flutterView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  49. flutterView.heightAnchor.constraint(equalToConstant: CGFloat(titleBarHeight + titleBarTopPadding)),
  50. ]
  51. NSLayoutConstraint.activate(constraints)
  52. view.addSubview(webView)
  53. webView.translatesAutoresizingMaskIntoConstraints = false
  54. NSLayoutConstraint.activate([
  55. webView.topAnchor.constraint(equalTo: flutterView.bottomAnchor),
  56. webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  57. webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
  58. webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  59. ])
  60. }
  61. override func viewDidLoad() {
  62. super.viewDidLoad()
  63. webView.navigationDelegate = self
  64. webView.uiDelegate = self
  65. // TODO(boyan01) Make it configuable from flutter.
  66. webView.configuration.preferences.javaEnabled = true
  67. webView.configuration.preferences.minimumFontSize = 12
  68. webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
  69. webView.configuration.allowsAirPlayForMediaPlayback = true
  70. webView.configuration.mediaTypesRequiringUserActionForPlayback = .video
  71. webView.addObserver(self, forKeyPath: "canGoBack", options: .new, context: nil)
  72. webView.addObserver(self, forKeyPath: "canGoForward", options: .new, context: nil)
  73. webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
  74. defaultUserAgent = webView.value(forKey: "userAgent") as? String
  75. }
  76. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
  77. if keyPath == "canGoBack" || keyPath == "canGoForward" {
  78. methodChannel.invokeMethod("onHistoryChanged", arguments: [
  79. "id": viewId,
  80. "canGoBack": webView.canGoBack,
  81. "canGoForward": webView.canGoForward,
  82. ] as [String: Any])
  83. } else if keyPath == "loading" {
  84. if webView.isLoading {
  85. methodChannel.invokeMethod("onNavigationStarted", arguments: [
  86. "id": viewId,
  87. ])
  88. } else {
  89. methodChannel.invokeMethod("onNavigationCompleted", arguments: [
  90. "id": viewId,
  91. ])
  92. }
  93. }
  94. }
  95. func load(url: URL) {
  96. debugPrint("load url: \(url)")
  97. webView.load(URLRequest(url: url))
  98. }
  99. func addJavascriptInterface(name: String) {
  100. javaScriptHandlerNames.append(name)
  101. webView.configuration.userContentController.add(self, name: name)
  102. }
  103. func removeJavascriptInterface(name: String) {
  104. if let index = javaScriptHandlerNames.firstIndex(of: name) {
  105. javaScriptHandlerNames.remove(at: index)
  106. }
  107. webView.configuration.userContentController.removeScriptMessageHandler(forName: name)
  108. }
  109. func addScriptToExecuteOnDocumentCreated(javaScript: String) {
  110. webView.configuration.userContentController.addUserScript(
  111. WKUserScript(source: javaScript, injectionTime: .atDocumentStart, forMainFrameOnly: true))
  112. }
  113. func setApplicationNameForUserAgent(applicationName: String) {
  114. webView.customUserAgent = (defaultUserAgent ?? "") + applicationName
  115. }
  116. func destroy() {
  117. webView.removeObserver(self, forKeyPath: "canGoBack")
  118. webView.removeObserver(self, forKeyPath: "canGoForward")
  119. webView.removeObserver(self, forKeyPath: "loading")
  120. webView.uiDelegate = nil
  121. webView.navigationDelegate = nil
  122. javaScriptHandlerNames.forEach { name in
  123. webView.configuration.userContentController.removeScriptMessageHandler(forName: name)
  124. }
  125. webView.configuration.userContentController.removeAllUserScripts()
  126. }
  127. func reload() {
  128. webView.reload()
  129. }
  130. func goBack() {
  131. if webView.canGoBack {
  132. webView.goBack()
  133. }
  134. }
  135. func goForward() {
  136. if webView.canGoForward {
  137. webView.goForward()
  138. }
  139. }
  140. func stopLoading() {
  141. webView.stopLoading()
  142. }
  143. func evaluateJavaScript(javaScriptString: String, completer: @escaping FlutterResult) {
  144. webView.evaluateJavaScript(javaScriptString) { result, error in
  145. if let error = error {
  146. completer(FlutterError(code: "1", message: error.localizedDescription, details: nil))
  147. return
  148. }
  149. completer(result)
  150. }
  151. }
  152. }
  153. extension WebViewLayoutController: WKNavigationDelegate {
  154. func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  155. guard let url = navigationAction.request.url else {
  156. decisionHandler(.cancel)
  157. return
  158. }
  159. guard ["http", "https", "file"].contains(url.scheme?.lowercased() ?? "") else {
  160. decisionHandler(.cancel)
  161. return
  162. }
  163. methodChannel.invokeMethod("onUrlRequested", arguments: [
  164. "id": viewId,
  165. "url": url.absoluteString,
  166. ] as [String: Any])
  167. decisionHandler(.allow)
  168. }
  169. func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
  170. decisionHandler(.allow)
  171. }
  172. }
  173. extension WebViewLayoutController: WKUIDelegate {
  174. func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
  175. methodChannel.invokeMethod(
  176. "runJavaScriptTextInputPanelWithPrompt",
  177. arguments: [
  178. "id": viewId,
  179. "prompt": prompt,
  180. "defaultText": defaultText ?? "",
  181. ] as [String: Any]) { result in
  182. completionHandler((result as? String) ?? "")
  183. }
  184. }
  185. func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
  186. if !(navigationAction.targetFrame?.isMainFrame ?? false) {
  187. webView.load(navigationAction.request)
  188. }
  189. return nil
  190. }
  191. }
  192. extension WebViewLayoutController: WKScriptMessageHandler {
  193. func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  194. methodChannel.invokeMethod(
  195. "onJavaScriptMessage",
  196. arguments: [
  197. "id": viewId,
  198. "name": message.name,
  199. "body": message.body,
  200. ] as [String: Any])
  201. }
  202. }