also looking at this
fix(ios): resolve openSettings' selector before the lease, and say why two actions have no deadline
#251
2 files
+25
-2
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.
| @@ -530,6 +530,13 @@ pub const PermissionsBridge = struct { | ||
| 530 | 530 | const options = objc.msgSendId(NSDictionary, sel_dictionary) orelse |
| 531 | 531 | return bridge_error.BridgeError.NativeCallFailed; |
| 532 | 532 | |
| 533 | // Resolved before the lease (#223). It was looked up after | |
| 534 | // `publishSettingsCall`, so a failure here released the slot through | |
| 535 | // the errdefer below but left the ticket parked in `settings_calls` | |
| 536 | // for a call that had already been refused. | |
| 537 | const sel_open = objc.sel_registerName("openURL:options:completionHandler:") orelse | |
| 538 | return bridge_error.BridgeError.NativeCallFailed; | |
| 539 | ||
| 533 | 540 | const ticket = ios_async.acquire(A.open_settings) orelse { |
| 534 | 541 | std.log.warn( |
| 535 | 542 | "openSettings: no free reply slot; {d} native calls are already awaiting one", |
| @@ -540,8 +547,15 @@ pub const PermissionsBridge = struct { | ||
| 540 | 547 | errdefer ios_async.abandon(ticket); |
| 541 | 548 | publishSettingsCall(ticket); |
| 542 | 549 | |
| 543 | const sel_open = objc.sel_registerName("openURL:options:completionHandler:") orelse | |
| 544 | return bridge_error.BridgeError.NativeCallFailed; | |
| 550 | // No deadline, deliberately, and #223 lists this action for one. The | |
| 551 | // app is suspended while Settings is open, and the uptime clock is | |
| 552 | // not: a deadline shorter than the visit fires on resume and races the | |
| 553 | // completion that was held back with it, answering TIMEOUT for an open | |
| 554 | // that succeeded. People go to Settings to change something, so a | |
| 555 | // visit longer than any sensible deadline is the ordinary case, not | |
| 556 | // the edge. `openURL:options:completionHandler:` is called by UIKit on | |
| 557 | // every open; the side table below is safe without a deadline because | |
| 558 | // nothing frees this slot early. | |
| 545 | 559 | const OpenFn = *const fn (objc.id, objc.SEL, objc.id, objc.id, *anyopaque) callconv(.c) void; |
| 546 | 560 | const openFn: OpenFn = @ptrCast(&objc.objc_msgSend); |
| 547 | 561 | openFn(app, sel_open, url, options, @ptrCast(&settings_blocks[ticket.index])); |
| @@ -376,6 +376,15 @@ pub const uikit = if (is_darwin) struct { | ||
| 376 | 376 | /// app has not listed in `LSApplicationQueriesSchemes`, so gating on it |
| 377 | 377 | /// would refuse URLs that `open:` would in fact have opened. The completion |
| 378 | 378 | /// handler's BOOL is the only truthful answer available. |
| 379 | /// | |
| 380 | /// No deadline, deliberately, although #223 lists this action for one. | |
| 381 | /// Its reason for wanting one does not apply here: this call leases no | |
| 382 | /// `ios_async` reply slot, so there is no slot for a deadline to free and | |
| 383 | /// no next call to hand the block to. The request id travels in the block | |
| 384 | /// itself, and `openURL:options:completionHandler:` heap-copies it. What a | |
| 385 | /// deadline *would* do is time out a person: `tel:` and `facetime:` put a | |
| 386 | /// "Call …?" confirmation on screen, and the completion waits for their | |
| 387 | /// answer. | |
| 379 | 388 | fn openURL(allocator: std.mem.Allocator, url_string: []const u8, request_id: i64) !void { |
| 380 | 389 | const UIApplication = objc.objc_getClass("UIApplication") orelse return error.ClassNotFound; |
| 381 | 390 | const sel_shared = objc.sel_registerName("sharedApplication") orelse return error.SelectorNotFound; |