ReviewOS

also looking at this

craft-native/craft

fix(ios): resolve openSettings' selector before the lease, and say why two actions have no deadline

#251
Merged glennmichael123 wants to merge fix/ios-open-settings-lease into main
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.

packages/zig/src/bridge_mobile_permissions.zigmodified+16-2
Changes to packages/zig/src/bridge_mobile_permissions.zig
@@ -530,6 +530,13 @@ pub const PermissionsBridge = struct {
530530 const options = objc.msgSendId(NSDictionary, sel_dictionary) orelse
531531 return bridge_error.BridgeError.NativeCallFailed;
532532
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
533540 const ticket = ios_async.acquire(A.open_settings) orelse {
534541 std.log.warn(
535542 "openSettings: no free reply slot; {d} native calls are already awaiting one",
@@ -540,8 +547,15 @@ pub const PermissionsBridge = struct {
540547 errdefer ios_async.abandon(ticket);
541548 publishSettingsCall(ticket);
542549
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.
545559 const OpenFn = *const fn (objc.id, objc.SEL, objc.id, objc.id, *anyopaque) callconv(.c) void;
546560 const openFn: OpenFn = @ptrCast(&objc.objc_msgSend);
547561 openFn(app, sel_open, url, options, @ptrCast(&settings_blocks[ticket.index]));
packages/zig/src/bridge_mobile_system.zigmodified+9-0
Changes to packages/zig/src/bridge_mobile_system.zig
@@ -376,6 +376,15 @@ pub const uikit = if (is_darwin) struct {
376376 /// app has not listed in `LSApplicationQueriesSchemes`, so gating on it
377377 /// would refuse URLs that `open:` would in fact have opened. The completion
378378 /// 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.
379388 fn openURL(allocator: std.mem.Allocator, url_string: []const u8, request_id: i64) !void {
380389 const UIApplication = objc.objc_getClass("UIApplication") orelse return error.ClassNotFound;
381390 const sel_shared = objc.sel_registerName("sharedApplication") orelse return error.SelectorNotFound;