also looking at this
fix(native): interaction, audio, document and checkpoint gaps stranded after #401
#415
19 files
+987
-50
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -1387,7 +1387,8 @@ dot. Non-JSON values and invalid names are rejected before a write is posted; | ||
| 1387 | 1387 | the previous confirmed checkpoint remains available after a failed or |
| 1388 | 1388 | interrupted replacement. If a screen closes while a checkpoint is still being |
| 1389 | 1389 | written, its promise rejects instead of hanging; the host's later worker |
| 1390 | completion cannot publish a second result for that request. | |
| 1390 | completion cannot publish a second result for that request. iOS also reports a | |
| 1391 | failed result when its writer completes after the screen runtime is disposed. | |
| 1391 | 1392 | |
| 1392 | 1393 | The `media` capability (iOS) keeps video and audio on the phone for offline, |
| 1393 | 1394 | in Application Support rather than the temporary directory, left out of |
| @@ -543,6 +543,8 @@ describe('Craft iOS builder', () => { | ||
| 543 | 543 | expect(native).toContain('pendingCapabilityDeadlines.values.forEach { $0.cancel() }') |
| 544 | 544 | expect(native).toContain('CraftNativeScriptThread.run { timers.pending.values.forEach { $0.cancel() } }') |
| 545 | 545 | expect(native).toContain('pendingCapabilityRequests.forEach { CraftNativeActions.cancel(requestToken: $0) }') |
| 546 | expect(native).toContain('self?.runtimeDisposed == true ? false : ok') | |
| 547 | expect(native).toContain('never as a successful write into a dead runtime') | |
| 546 | 548 | expect(native).toContain('lifecycleObservers.forEach(NotificationCenter.default.removeObserver)') |
| 547 | 549 | expect(native).not.toContain('WKWebView') |
| 548 | 550 | }) |
| @@ -6407,14 +6407,21 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate, | ||
| 6407 | 6407 | CraftSnapshots.read(name) |
| 6408 | 6408 | } |
| 6409 | 6409 | let snapshotSet: @convention(block) (String, JSValue, JSValue) -> Void = { name, value, done in |
| 6410 | CraftSnapshots.write(name, json: value.isNull || value.isUndefined ? nil : value.toString()) { ok in | |
| 6411 | CraftNativeScriptThread.run { done.call(withArguments: [ok]) } | |
| 6410 | CraftSnapshots.write(name, json: value.isNull || value.isUndefined ? nil : value.toString()) { [weak self] ok in | |
| 6411 | CraftNativeScriptThread.run { | |
| 6412 | // A writer can finish after UIKit has disposed this | |
| 6413 | // screen. Resolve the pending promise as a cancellation, | |
| 6414 | // never as a successful write into a dead runtime. | |
| 6415 | done.call(withArguments: [self?.runtimeDisposed == true ? false : ok]) | |
| 6416 | } | |
| 6412 | 6417 | } |
| 6413 | 6418 | } |
| 6414 | 6419 | let snapshotClear: @convention(block) (JSValue, JSValue) -> Void = { keep, done in |
| 6415 | 6420 | let names = (keep.isUndefined || keep.isNull ? [] : (keep.toArray() ?? [])).compactMap { $0 as? String } |
| 6416 | CraftSnapshots.clear(keeping: Set(names)) { ok in | |
| 6417 | CraftNativeScriptThread.run { done.call(withArguments: [ok]) } | |
| 6421 | CraftSnapshots.clear(keeping: Set(names)) { [weak self] ok in | |
| 6422 | CraftNativeScriptThread.run { | |
| 6423 | done.call(withArguments: [self?.runtimeDisposed == true ? false : ok]) | |
| 6424 | } | |
| 6418 | 6425 | } |
| 6419 | 6426 | } |
| 6420 | 6427 | let config = self.config |