@capgo/capacitor-inappbrowser 8.6.15 → 8.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +7 -1
- package/ios/Sources/InAppBrowserPlugin/ProxySchemeHandler.swift +112 -50
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +86 -50
- package/package.json +1 -1
|
@@ -63,7 +63,7 @@ import org.json.JSONObject;
|
|
|
63
63
|
)
|
|
64
64
|
public class InAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
|
|
65
65
|
|
|
66
|
-
private final String pluginVersion = "8.6.
|
|
66
|
+
private final String pluginVersion = "8.6.17";
|
|
67
67
|
|
|
68
68
|
public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome"; // Change when in stable
|
|
69
69
|
private CustomTabsClient customTabsClient;
|
|
@@ -77,7 +77,7 @@ public class InAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
77
77
|
case aware = "AWARE"
|
|
78
78
|
case fakeVisible = "FAKE_VISIBLE"
|
|
79
79
|
}
|
|
80
|
-
private let pluginVersion: String = "8.6.
|
|
80
|
+
private let pluginVersion: String = "8.6.17"
|
|
81
81
|
public let identifier = "InAppBrowserPlugin"
|
|
82
82
|
public let jsName = "InAppBrowser"
|
|
83
83
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -342,12 +342,18 @@ public class InAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
342
342
|
return
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
dismissActiveKeyboard()
|
|
345
346
|
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
346
347
|
presenter?.present(navigationController, animated: isAnimated, completion: {
|
|
347
348
|
self.currentPluginCall?.resolve()
|
|
348
349
|
})
|
|
349
350
|
}
|
|
350
351
|
|
|
352
|
+
private func dismissActiveKeyboard() {
|
|
353
|
+
self.bridge?.webView?.endEditing(true)
|
|
354
|
+
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
355
|
+
}
|
|
356
|
+
|
|
351
357
|
private func activeWindow() -> UIWindow? {
|
|
352
358
|
return UIApplication.shared.connectedScenes
|
|
353
359
|
.compactMap { $0 as? UIWindowScene }
|
|
@@ -403,6 +403,7 @@ enum ProxySchemeRequestSupport {
|
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
final class PendingProxyTask {
|
|
406
|
+
let requestId: String
|
|
406
407
|
let schemeTask: WKURLSchemeTask
|
|
407
408
|
var requestContext: NativeRequestContext
|
|
408
409
|
var responseData: NativeResponseData?
|
|
@@ -411,12 +412,32 @@ final class PendingProxyTask {
|
|
|
411
412
|
var redirectRequest: URLRequest?
|
|
412
413
|
var timeoutToken: UUID?
|
|
413
414
|
var canceled = false
|
|
414
|
-
|
|
415
|
-
|
|
415
|
+
/// Set true when WebKit calls `webView(_:stop:)` for this task. Once stopped,
|
|
416
|
+
/// the `WKURLSchemeTask` must never receive `didReceive`/`didFinish`/`didFailWithError`
|
|
417
|
+
/// again or WebKit raises an uncatchable NSException.
|
|
418
|
+
var isStopped = false
|
|
419
|
+
/// Set true the first time a terminal scheme-task call sequence is committed.
|
|
420
|
+
/// Guarantees at-most-once completion even when multiple async sources
|
|
421
|
+
/// (URLSession delegate queue, cookie-sync main hop, global timeout, cancel-all)
|
|
422
|
+
/// race to finish the same request.
|
|
423
|
+
private var isComplete = false
|
|
424
|
+
|
|
425
|
+
init(requestId: String, schemeTask: WKURLSchemeTask, requestContext: NativeRequestContext, phase: String) {
|
|
426
|
+
self.requestId = requestId
|
|
416
427
|
self.schemeTask = schemeTask
|
|
417
428
|
self.requestContext = requestContext
|
|
418
429
|
self.phase = phase
|
|
419
430
|
}
|
|
431
|
+
|
|
432
|
+
/// Claims the right to issue the single allowed terminal call sequence on the
|
|
433
|
+
/// scheme task. Returns true exactly once, and only while the task is live
|
|
434
|
+
/// (not stopped). Callers MUST hold the handler's `taskLock` so the claim is
|
|
435
|
+
/// atomic with respect to `webView(_:stop:)` marking the task stopped.
|
|
436
|
+
func beginCompletionIfLive() -> Bool {
|
|
437
|
+
if isComplete || isStopped { return false }
|
|
438
|
+
isComplete = true
|
|
439
|
+
return true
|
|
440
|
+
}
|
|
420
441
|
}
|
|
421
442
|
|
|
422
443
|
// swiftlint:disable type_body_length
|
|
@@ -491,7 +512,7 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
491
512
|
base64Body: extractBody(from: urlSchemeTask.request),
|
|
492
513
|
isMainFrame: ProxySchemeRequestSupport.isMainFrameRequest(urlSchemeTask.request)
|
|
493
514
|
)
|
|
494
|
-
let pendingTask = PendingProxyTask(schemeTask: urlSchemeTask, requestContext: requestContext, phase: "outbound")
|
|
515
|
+
let pendingTask = PendingProxyTask(requestId: requestId, schemeTask: urlSchemeTask, requestContext: requestContext, phase: "outbound")
|
|
495
516
|
|
|
496
517
|
taskLock.lock()
|
|
497
518
|
pendingTasks[requestId] = pendingTask
|
|
@@ -503,6 +524,7 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
503
524
|
public func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
|
|
504
525
|
taskLock.lock()
|
|
505
526
|
if let entry = pendingTasks.first(where: { $0.value.schemeTask === urlSchemeTask }) {
|
|
527
|
+
entry.value.isStopped = true
|
|
506
528
|
pendingTasks.removeValue(forKey: entry.key)
|
|
507
529
|
let cleanupToken = UUID()
|
|
508
530
|
stoppedRequests[entry.key] = cleanupToken
|
|
@@ -573,7 +595,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
573
595
|
|
|
574
596
|
if pendingTask.canceled {
|
|
575
597
|
finishWithCanceledResponse(task: pendingTask)
|
|
576
|
-
removePendingTask(requestId: requestId)
|
|
577
598
|
return
|
|
578
599
|
}
|
|
579
600
|
|
|
@@ -583,7 +604,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
583
604
|
) {
|
|
584
605
|
case .finishCachedResponse:
|
|
585
606
|
guard let responseData = pendingTask.responseData else { return }
|
|
586
|
-
removePendingTask(requestId: requestId)
|
|
587
607
|
syncResponseCookies(from: responseData, fallbackURL: pendingTask.requestContext.url) {
|
|
588
608
|
self.finish(task: pendingTask, with: responseData)
|
|
589
609
|
}
|
|
@@ -600,14 +620,14 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
600
620
|
do {
|
|
601
621
|
request = try makeURLRequest(from: pendingTask.requestContext)
|
|
602
622
|
} catch {
|
|
603
|
-
|
|
604
|
-
|
|
623
|
+
failSchemeTask(
|
|
624
|
+
pendingTask,
|
|
625
|
+
with: NSError(
|
|
605
626
|
domain: "ProxySchemeHandler",
|
|
606
627
|
code: -3,
|
|
607
628
|
userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]
|
|
608
629
|
)
|
|
609
630
|
)
|
|
610
|
-
removePendingTask(requestId: requestId)
|
|
611
631
|
return
|
|
612
632
|
}
|
|
613
633
|
let task = session.dataTask(with: request) { [weak self] data, response, error in
|
|
@@ -615,13 +635,18 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
615
635
|
guard let pendingTask = self.pendingTask(for: requestId) else { return }
|
|
616
636
|
|
|
617
637
|
if let error {
|
|
618
|
-
if (error as NSError).code == NSURLErrorCancelled
|
|
638
|
+
if (error as NSError).code == NSURLErrorCancelled {
|
|
639
|
+
// The data task is only canceled by webView(_:stop:) or
|
|
640
|
+
// cancelAllPendingTasks(), and both own the task's lifecycle: stop has
|
|
641
|
+
// already removed it (and marked it stopped) before canceling, while
|
|
642
|
+
// cancelAll routes a terminal failSchemeTask through completeSchemeTask,
|
|
643
|
+
// which removes it on the main queue. Removing it here on the URLSession
|
|
644
|
+
// background queue would unhook it from pendingTasks before a racing
|
|
645
|
+
// stop could mark it stopped, reopening the post-stop completion crash.
|
|
646
|
+
// So just stop processing and let the owning path complete it.
|
|
619
647
|
return
|
|
620
648
|
}
|
|
621
|
-
|
|
622
|
-
pendingTask.schemeTask.didFailWithError(error)
|
|
623
|
-
}
|
|
624
|
-
self.removePendingTask(requestId: requestId)
|
|
649
|
+
self.failSchemeTask(pendingTask, with: error)
|
|
625
650
|
return
|
|
626
651
|
}
|
|
627
652
|
|
|
@@ -680,13 +705,11 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
680
705
|
switch inboundRule.action {
|
|
681
706
|
case .cancel:
|
|
682
707
|
finishWithCanceledResponse(task: pendingTask)
|
|
683
|
-
removePendingTask(requestId: requestId)
|
|
684
708
|
case .continue:
|
|
685
709
|
if pendingTask.redirectRequest != nil {
|
|
686
710
|
followPendingRedirect(requestId: requestId)
|
|
687
711
|
} else {
|
|
688
712
|
finish(task: pendingTask, with: responseData)
|
|
689
|
-
removePendingTask(requestId: requestId)
|
|
690
713
|
}
|
|
691
714
|
case .delegateToJs:
|
|
692
715
|
emitProxyEvent(requestId: requestId, pendingTask: pendingTask)
|
|
@@ -697,7 +720,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
697
720
|
followPendingRedirect(requestId: requestId)
|
|
698
721
|
} else {
|
|
699
722
|
finish(task: pendingTask, with: responseData)
|
|
700
|
-
removePendingTask(requestId: requestId)
|
|
701
723
|
}
|
|
702
724
|
}
|
|
703
725
|
}
|
|
@@ -759,7 +781,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
759
781
|
self.fallbackToNativePipeline(requestId: requestId)
|
|
760
782
|
return
|
|
761
783
|
case .finishCachedResponse:
|
|
762
|
-
self.pendingTasks.removeValue(forKey: requestId)
|
|
763
784
|
self.taskLock.unlock()
|
|
764
785
|
guard let responseData else { return }
|
|
765
786
|
self.syncResponseCookies(from: responseData, fallbackURL: pendingTask.requestContext.url) {
|
|
@@ -767,12 +788,12 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
767
788
|
}
|
|
768
789
|
return
|
|
769
790
|
case .failRequest:
|
|
770
|
-
self.pendingTasks.removeValue(forKey: requestId)
|
|
771
791
|
self.taskLock.unlock()
|
|
772
792
|
}
|
|
773
793
|
|
|
774
|
-
|
|
775
|
-
|
|
794
|
+
self.failSchemeTask(
|
|
795
|
+
pendingTask,
|
|
796
|
+
with: NSError(
|
|
776
797
|
domain: "ProxySchemeHandler",
|
|
777
798
|
code: NSURLErrorTimedOut,
|
|
778
799
|
userInfo: [NSLocalizedDescriptionKey: "Proxy handler did not respond within \(Int(self.proxyTimeoutSeconds)) seconds"]
|
|
@@ -825,14 +846,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
825
846
|
return task
|
|
826
847
|
}
|
|
827
848
|
|
|
828
|
-
private func removePendingTask(requestId: String) {
|
|
829
|
-
taskLock.lock()
|
|
830
|
-
pendingTasks.removeValue(forKey: requestId)
|
|
831
|
-
stoppedRequests.removeValue(forKey: requestId)
|
|
832
|
-
timedOutRequests.removeValue(forKey: requestId)
|
|
833
|
-
taskLock.unlock()
|
|
834
|
-
}
|
|
835
|
-
|
|
836
849
|
private func routeCurrentRequest(requestId: String) {
|
|
837
850
|
guard let pendingTask = pendingTask(for: requestId) else { return }
|
|
838
851
|
pendingTask.phase = "outbound"
|
|
@@ -842,7 +855,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
842
855
|
switch outboundRule.action {
|
|
843
856
|
case .cancel:
|
|
844
857
|
finishWithCanceledResponse(task: pendingTask)
|
|
845
|
-
removePendingTask(requestId: requestId)
|
|
846
858
|
case .continue:
|
|
847
859
|
executeNativePipeline(requestId: requestId)
|
|
848
860
|
case .delegateToJs:
|
|
@@ -1022,27 +1034,73 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
1022
1034
|
)
|
|
1023
1035
|
}
|
|
1024
1036
|
|
|
1037
|
+
/// Runs a terminal `WKURLSchemeTask` call sequence at most once, and never after
|
|
1038
|
+
/// the task has been stopped.
|
|
1039
|
+
///
|
|
1040
|
+
/// `WKURLSchemeTask` raises an uncatchable NSException if `didReceive`/`didFinish`/
|
|
1041
|
+
/// `didFailWithError` is called after `webView(_:stop:)`, after the task already
|
|
1042
|
+
/// completed, or out of order. Completion can be triggered from several racing
|
|
1043
|
+
/// sources — the URLSession delegate queue, the cookie-sync main-thread hop, the
|
|
1044
|
+
/// global timeout closure, `webView(_:stop:)`, and `cancelAllPendingTasks()`. This
|
|
1045
|
+
/// helper funnels every terminal call onto the main thread (the same queue WebKit
|
|
1046
|
+
/// uses for `start`/`stop`) and gates it behind `beginCompletionIfLive()`, so the
|
|
1047
|
+
/// liveness check and the actual call cannot be separated by a `stop`.
|
|
1048
|
+
///
|
|
1049
|
+
/// This helper is also the single owner of task removal. Callers MUST NOT remove
|
|
1050
|
+
/// the task from `pendingTasks` eagerly on a background thread before calling a
|
|
1051
|
+
/// terminal method: doing so creates a window where `webView(_:stop:)` (which only
|
|
1052
|
+
/// finds tasks via `pendingTasks`) can no longer mark the task stopped, so a queued
|
|
1053
|
+
/// completion would then call `didReceive`/`didFinish`/`didFailWithError` on an
|
|
1054
|
+
/// already-stopped task and crash. Keeping the task tracked until this main-queue
|
|
1055
|
+
/// block runs lets a racing `stop` win the liveness claim.
|
|
1056
|
+
private func completeSchemeTask(_ task: PendingProxyTask, _ body: @escaping (WKURLSchemeTask) -> Void) {
|
|
1057
|
+
let runBody: () -> Void = { [weak self] in
|
|
1058
|
+
guard let self else { return }
|
|
1059
|
+
self.taskLock.lock()
|
|
1060
|
+
let isLive = task.beginCompletionIfLive()
|
|
1061
|
+
self.pendingTasks.removeValue(forKey: task.requestId)
|
|
1062
|
+
self.stoppedRequests.removeValue(forKey: task.requestId)
|
|
1063
|
+
self.timedOutRequests.removeValue(forKey: task.requestId)
|
|
1064
|
+
self.taskLock.unlock()
|
|
1065
|
+
guard isLive else { return }
|
|
1066
|
+
body(task.schemeTask)
|
|
1067
|
+
}
|
|
1068
|
+
if Thread.isMainThread {
|
|
1069
|
+
runBody()
|
|
1070
|
+
} else {
|
|
1071
|
+
DispatchQueue.main.async(execute: runBody)
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
private func failSchemeTask(_ task: PendingProxyTask, with error: Error) {
|
|
1076
|
+
completeSchemeTask(task) { schemeTask in
|
|
1077
|
+
schemeTask.didFailWithError(error)
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1025
1081
|
private func finish(task: PendingProxyTask, with responseData: NativeResponseData) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1082
|
+
completeSchemeTask(task) { schemeTask in
|
|
1083
|
+
guard let url = URL(string: task.requestContext.url),
|
|
1084
|
+
let httpResponse = HTTPURLResponse(
|
|
1085
|
+
url: url,
|
|
1086
|
+
statusCode: responseData.statusCode,
|
|
1087
|
+
httpVersion: "HTTP/1.1",
|
|
1088
|
+
headerFields: responseData.headers
|
|
1089
|
+
)
|
|
1090
|
+
else {
|
|
1091
|
+
schemeTask.didFailWithError(
|
|
1092
|
+
NSError(
|
|
1093
|
+
domain: "ProxySchemeHandler",
|
|
1094
|
+
code: -2,
|
|
1095
|
+
userInfo: [NSLocalizedDescriptionKey: "Failed to create response"]
|
|
1096
|
+
)
|
|
1039
1097
|
)
|
|
1040
|
-
|
|
1041
|
-
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
schemeTask.didReceive(httpResponse)
|
|
1101
|
+
schemeTask.didReceive(responseData.body)
|
|
1102
|
+
schemeTask.didFinish()
|
|
1042
1103
|
}
|
|
1043
|
-
task.schemeTask.didReceive(httpResponse)
|
|
1044
|
-
task.schemeTask.didReceive(responseData.body)
|
|
1045
|
-
task.schemeTask.didFinish()
|
|
1046
1104
|
}
|
|
1047
1105
|
|
|
1048
1106
|
private func finishWithCanceledResponse(task: PendingProxyTask) {
|
|
@@ -1138,15 +1196,19 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
|
|
|
1138
1196
|
func cancelAllPendingTasks() {
|
|
1139
1197
|
taskLock.lock()
|
|
1140
1198
|
let pending = pendingTasks
|
|
1141
|
-
pendingTasks.
|
|
1199
|
+
// Do not clear pendingTasks here. completeSchemeTask removes each task on the
|
|
1200
|
+
// main queue once its failure is claimed, so a concurrent webView(_:stop:) can
|
|
1201
|
+
// still find and mark the task stopped before the queued failure runs. Clearing
|
|
1202
|
+
// eagerly would reopen the post-stop completion crash this fix closes.
|
|
1142
1203
|
stoppedRequests.removeAll()
|
|
1143
1204
|
taskLock.unlock()
|
|
1144
1205
|
session.invalidateAndCancel()
|
|
1145
1206
|
|
|
1146
1207
|
for (_, task) in pending {
|
|
1147
1208
|
task.urlSessionTask?.cancel()
|
|
1148
|
-
|
|
1149
|
-
|
|
1209
|
+
failSchemeTask(
|
|
1210
|
+
task,
|
|
1211
|
+
with: NSError(
|
|
1150
1212
|
domain: "ProxySchemeHandler",
|
|
1151
1213
|
code: NSURLErrorCancelled,
|
|
1152
1214
|
userInfo: [NSLocalizedDescriptionKey: "WebView closed"]
|
|
@@ -226,6 +226,16 @@ enum ConsoleMessageSupport {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
enum WebViewViewportLayoutSupport {
|
|
230
|
+
static func shouldRefreshViewport(previousSize: CGSize?, currentSize: CGSize, force: Bool = false) -> Bool {
|
|
231
|
+
guard currentSize.width > 0, currentSize.height > 0 else {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return force || previousSize != currentSize
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
229
239
|
open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
230
240
|
|
|
231
241
|
public init() {
|
|
@@ -343,9 +353,6 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
343
353
|
var shareDisclaimer: [String: Any]?
|
|
344
354
|
var shareSubject: String?
|
|
345
355
|
var didpageInit = false
|
|
346
|
-
var viewHeightLandscape: CGFloat?
|
|
347
|
-
var viewHeightPortrait: CGFloat?
|
|
348
|
-
var currentViewHeight: CGFloat?
|
|
349
356
|
open var closeModal = false
|
|
350
357
|
open var closeModalTitle = ""
|
|
351
358
|
open var closeModalDescription = ""
|
|
@@ -386,6 +393,8 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
386
393
|
internal var preShowSemaphore: DispatchSemaphore?
|
|
387
394
|
internal var preShowError: String?
|
|
388
395
|
private var isWebViewInitialized = false
|
|
396
|
+
private var isObservingKeyboardViewportChanges = false
|
|
397
|
+
private var lastViewportRefreshSize: CGSize?
|
|
389
398
|
private var downloadStates: [ObjectIdentifier: WKDownloadState] = [:]
|
|
390
399
|
private var previewItemURL: URL?
|
|
391
400
|
|
|
@@ -432,10 +441,10 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
432
441
|
|
|
433
442
|
private func attachmentDispositionType(_ response: URLResponse) -> String? {
|
|
434
443
|
guard let disposition = attachmentDisposition(response)?
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
444
|
+
.split(separator: ";", maxSplits: 1)
|
|
445
|
+
.first?
|
|
446
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
447
|
+
.lowercased(),
|
|
439
448
|
!disposition.isEmpty else {
|
|
440
449
|
return nil
|
|
441
450
|
}
|
|
@@ -527,7 +536,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
527
536
|
"application/download",
|
|
528
537
|
"application/x-download",
|
|
529
538
|
"application/binary",
|
|
530
|
-
"application/x-binary"
|
|
539
|
+
"application/x-binary"
|
|
531
540
|
]
|
|
532
541
|
|
|
533
542
|
if let candidate, !candidate.isEmpty, !genericMimeTypes.contains(candidate) {
|
|
@@ -590,7 +599,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
590
599
|
"fileName": fileURL.lastPathComponent,
|
|
591
600
|
"path": fileURL.path,
|
|
592
601
|
"localUrl": fileURL.absoluteString,
|
|
593
|
-
"handledBy": handledBy
|
|
602
|
+
"handledBy": handledBy
|
|
594
603
|
]
|
|
595
604
|
if let sourceURL {
|
|
596
605
|
data["sourceUrl"] = sourceURL
|
|
@@ -842,6 +851,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
842
851
|
}
|
|
843
852
|
|
|
844
853
|
deinit {
|
|
854
|
+
NotificationCenter.default.removeObserver(self)
|
|
845
855
|
webView?.removeObserver(self, forKeyPath: estimatedProgressKeyPath)
|
|
846
856
|
if websiteTitleInNavigationBar {
|
|
847
857
|
webView?.removeObserver(self, forKeyPath: titleKeyPath)
|
|
@@ -886,6 +896,11 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
886
896
|
}
|
|
887
897
|
}
|
|
888
898
|
|
|
899
|
+
override open func viewDidLayoutSubviews() {
|
|
900
|
+
super.viewDidLayoutSubviews()
|
|
901
|
+
refreshWebViewViewportIfNeeded()
|
|
902
|
+
}
|
|
903
|
+
|
|
889
904
|
func updateButtonTintColors() {
|
|
890
905
|
// Ensure all button items use the navigation bar's tint color
|
|
891
906
|
if let tintColor = navigationController?.navigationBar.tintColor {
|
|
@@ -986,6 +1001,63 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
986
1001
|
capBrowserPlugin?.notifyListeners(eventName, data: payload(with: data))
|
|
987
1002
|
}
|
|
988
1003
|
|
|
1004
|
+
private func startObservingKeyboardViewportChanges() {
|
|
1005
|
+
guard !isObservingKeyboardViewportChanges else {
|
|
1006
|
+
return
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
isObservingKeyboardViewportChanges = true
|
|
1010
|
+
let center = NotificationCenter.default
|
|
1011
|
+
center.addObserver(self, selector: #selector(keyboardViewportDidChange(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
|
|
1012
|
+
center.addObserver(self, selector: #selector(keyboardViewportDidChange(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
|
|
1013
|
+
center.addObserver(self, selector: #selector(keyboardViewportDidChange(_:)), name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
@objc private func keyboardViewportDidChange(_ notification: Notification) {
|
|
1017
|
+
scheduleWebViewViewportRefresh(force: true, delay: viewportRefreshDelay(from: notification))
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
private func viewportRefreshDelay(from notification: Notification) -> TimeInterval {
|
|
1021
|
+
guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else {
|
|
1022
|
+
return 0.05
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
return max(0.05, duration.doubleValue)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
private func scheduleWebViewViewportRefresh(force: Bool = false, delay: TimeInterval = 0.05) {
|
|
1029
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
1030
|
+
self?.refreshWebViewViewportIfNeeded(force: force)
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private func refreshWebViewViewportIfNeeded(force: Bool = false) {
|
|
1035
|
+
guard let webView = self.webView, webView.superview != nil else {
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if force {
|
|
1040
|
+
self.view.setNeedsLayout()
|
|
1041
|
+
self.view.layoutIfNeeded()
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
let currentSize = webView.bounds.size
|
|
1045
|
+
guard WebViewViewportLayoutSupport.shouldRefreshViewport(
|
|
1046
|
+
previousSize: lastViewportRefreshSize,
|
|
1047
|
+
currentSize: currentSize,
|
|
1048
|
+
force: force
|
|
1049
|
+
) else {
|
|
1050
|
+
return
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
lastViewportRefreshSize = currentSize
|
|
1054
|
+
webView.setNeedsLayout()
|
|
1055
|
+
webView.layoutIfNeeded()
|
|
1056
|
+
webView.scrollView.setNeedsLayout()
|
|
1057
|
+
webView.scrollView.layoutIfNeeded()
|
|
1058
|
+
webView.evaluateJavaScript("window.dispatchEvent(new Event('resize'));", completionHandler: nil)
|
|
1059
|
+
}
|
|
1060
|
+
|
|
989
1061
|
private func jsonString(from object: Any) -> String? {
|
|
990
1062
|
guard JSONSerialization.isValidJSONObject(object),
|
|
991
1063
|
let data = try? JSONSerialization.data(withJSONObject: object),
|
|
@@ -1251,6 +1323,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1251
1323
|
return
|
|
1252
1324
|
}
|
|
1253
1325
|
self.isWebViewInitialized = true
|
|
1326
|
+
self.startObservingKeyboardViewportChanges()
|
|
1254
1327
|
self.view.backgroundColor = UIColor.white
|
|
1255
1328
|
|
|
1256
1329
|
self.extendedLayoutIncludesOpaqueBars = true
|
|
@@ -1540,47 +1613,6 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1540
1613
|
return self.capableWebView
|
|
1541
1614
|
}
|
|
1542
1615
|
|
|
1543
|
-
@objc func restateViewHeight() {
|
|
1544
|
-
var bottomPadding = CGFloat(0.0)
|
|
1545
|
-
var topPadding = CGFloat(0.0)
|
|
1546
|
-
let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
|
|
1547
|
-
bottomPadding = window?.safeAreaInsets.bottom ?? 0.0
|
|
1548
|
-
topPadding = window?.safeAreaInsets.top ?? 0.0
|
|
1549
|
-
if UIDevice.current.orientation.isPortrait {
|
|
1550
|
-
// Don't force toolbar visibility
|
|
1551
|
-
if self.viewHeightPortrait == nil {
|
|
1552
|
-
self.viewHeightPortrait = self.view.safeAreaLayoutGuide.layoutFrame.size.height
|
|
1553
|
-
self.viewHeightPortrait! += bottomPadding
|
|
1554
|
-
if self.navigationController?.navigationBar.isHidden == true {
|
|
1555
|
-
self.viewHeightPortrait! += topPadding
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
self.currentViewHeight = self.viewHeightPortrait
|
|
1559
|
-
} else if UIDevice.current.orientation.isLandscape {
|
|
1560
|
-
// Don't force toolbar visibility
|
|
1561
|
-
if self.viewHeightLandscape == nil {
|
|
1562
|
-
self.viewHeightLandscape = self.view.safeAreaLayoutGuide.layoutFrame.size.height
|
|
1563
|
-
self.viewHeightLandscape! += bottomPadding
|
|
1564
|
-
if self.navigationController?.navigationBar.isHidden == true {
|
|
1565
|
-
self.viewHeightLandscape! += topPadding
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
self.currentViewHeight = self.viewHeightLandscape
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
|
1573
|
-
// self.view.frame.size.height = self.currentViewHeight!
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
override open func viewWillLayoutSubviews() {
|
|
1577
|
-
restateViewHeight()
|
|
1578
|
-
// Don't override frame height when enabledSafeBottomMargin is true, as it would override our constraints
|
|
1579
|
-
if self.currentViewHeight != nil && !self.enabledSafeBottomMargin {
|
|
1580
|
-
self.view.frame.size.height = self.currentViewHeight!
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
1616
|
override open func viewWillAppear(_ animated: Bool) {
|
|
1585
1617
|
super.viewWillAppear(animated)
|
|
1586
1618
|
if !self.viewWasPresented {
|
|
@@ -1620,6 +1652,8 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1620
1652
|
|
|
1621
1653
|
override open func viewDidAppear(_ animated: Bool) {
|
|
1622
1654
|
super.viewDidAppear(animated)
|
|
1655
|
+
refreshWebViewViewportIfNeeded(force: true)
|
|
1656
|
+
scheduleWebViewViewportRefresh(force: true, delay: 0.25)
|
|
1623
1657
|
|
|
1624
1658
|
// Force add buttonNearDone if it's not visible yet
|
|
1625
1659
|
if buttonNearDoneIcon != nil {
|
|
@@ -2907,6 +2941,7 @@ extension WKWebViewController: WKNavigationDelegate {
|
|
|
2907
2941
|
webView.topAnchor.constraint(equalTo: topAnchor)
|
|
2908
2942
|
])
|
|
2909
2943
|
self.view.layoutIfNeeded()
|
|
2944
|
+
refreshWebViewViewportIfNeeded(force: true)
|
|
2910
2945
|
}
|
|
2911
2946
|
|
|
2912
2947
|
open func updateSafeBottomMargin(_ enabled: Bool) {
|
|
@@ -2927,6 +2962,7 @@ extension WKWebViewController: WKNavigationDelegate {
|
|
|
2927
2962
|
webView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
2928
2963
|
])
|
|
2929
2964
|
self.view.layoutIfNeeded()
|
|
2965
|
+
refreshWebViewViewportIfNeeded(force: true)
|
|
2930
2966
|
}
|
|
2931
2967
|
}
|
|
2932
2968
|
|