InAppBrowserWindow.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import AppKit
  2. import WebKit
  3. final class InAppBrowserWindowManager {
  4. static let shared = InAppBrowserWindowManager()
  5. private var controllers: [InAppBrowserWindowController] = []
  6. private init() {}
  7. func open(url: URL, title: String? = nil) {
  8. let controller = InAppBrowserWindowController(initialURL: url, windowTitle: title)
  9. controllers.append(controller)
  10. controller.onClose = { [weak self, weak controller] in
  11. guard let self, let controller else { return }
  12. self.controllers.removeAll { $0 === controller }
  13. }
  14. controller.showWindow(nil)
  15. controller.window?.makeKeyAndOrderFront(nil)
  16. }
  17. }
  18. final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
  19. var onClose: (() -> Void)?
  20. private let webView: WKWebView
  21. private let initialURL: URL
  22. private var didLoadInitialURL = false
  23. init(initialURL: URL, windowTitle: String?) {
  24. self.initialURL = initialURL
  25. let config = WKWebViewConfiguration()
  26. config.defaultWebpagePreferences.allowsContentJavaScript = true
  27. self.webView = WKWebView(frame: .zero, configuration: config)
  28. let window = NSWindow(
  29. contentRect: NSRect(x: 180, y: 140, width: 1200, height: 800),
  30. styleMask: [.titled, .closable, .miniaturizable, .resizable],
  31. backing: .buffered,
  32. defer: false
  33. )
  34. window.title = windowTitle ?? "Browser"
  35. window.isReleasedWhenClosed = false
  36. window.contentView = webView
  37. super.init(window: window)
  38. window.delegate = self
  39. webView.uiDelegate = self
  40. webView.navigationDelegate = self
  41. webView.allowsBackForwardNavigationGestures = true
  42. webView.setValue(false, forKey: "drawsBackground")
  43. }
  44. @available(*, unavailable)
  45. required init?(coder: NSCoder) {
  46. nil
  47. }
  48. override func showWindow(_ sender: Any?) {
  49. super.showWindow(sender)
  50. guard !didLoadInitialURL else { return }
  51. didLoadInitialURL = true
  52. webView.load(URLRequest(url: initialURL))
  53. }
  54. func windowWillClose(_ notification: Notification) {
  55. onClose?()
  56. }
  57. }
  58. extension InAppBrowserWindowController: WKNavigationDelegate {
  59. func webView(
  60. _ webView: WKWebView,
  61. decidePolicyFor navigationAction: WKNavigationAction,
  62. decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
  63. ) {
  64. if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
  65. InAppBrowserWindowManager.shared.open(url: url)
  66. decisionHandler(.cancel)
  67. return
  68. }
  69. decisionHandler(.allow)
  70. }
  71. func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  72. if let host = webView.url?.host, !host.isEmpty {
  73. window?.title = host
  74. }
  75. }
  76. func webView(
  77. _ webView: WKWebView,
  78. didFailProvisionalNavigation navigation: WKNavigation!,
  79. withError error: Error
  80. ) {
  81. showLoadError(error)
  82. }
  83. func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
  84. showLoadError(error)
  85. }
  86. private func showLoadError(_ error: Error) {
  87. let message = """
  88. <html>
  89. <body style="background:#111;color:#EEE;font-family:-apple-system, sans-serif;padding:24px;">
  90. <h2>Page failed to load</h2>
  91. <p>Could not open <b>\(initialURL.absoluteString)</b></p>
  92. <p>\(error.localizedDescription)</p>
  93. </body>
  94. </html>
  95. """
  96. webView.loadHTMLString(message, baseURL: nil)
  97. window?.title = "Load Error"
  98. }
  99. }
  100. extension InAppBrowserWindowController: WKUIDelegate {
  101. func webView(
  102. _ webView: WKWebView,
  103. createWebViewWith configuration: WKWebViewConfiguration,
  104. for navigationAction: WKNavigationAction,
  105. windowFeatures: WKWindowFeatures
  106. ) -> WKWebView? {
  107. guard let url = navigationAction.request.url else { return nil }
  108. InAppBrowserWindowManager.shared.open(url: url)
  109. return nil
  110. }
  111. }