Skip to content

Commit e81d11c

Browse files
committed
Move to objc2 instead of objc. See PR #87
2 parents a16d6ae + 9184245 commit e81d11c

File tree

8 files changed

+115
-73
lines changed

8 files changed

+115
-73
lines changed

.github/workflows/android.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ env:
1717
jobs:
1818
build:
1919
name: Build
20-
runs-on: macos-latest
20+
runs-on: ubuntu-latest
2121
if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-android') }}
2222
strategy:
2323
matrix:

.github/workflows/ios.yaml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ on:
88

99
env:
1010
CARGO_TERM_COLOR: always
11-
RUST_BACKTRACE: 1
11+
RUST_BACKTRACE: full
1212
RUST_LOG: webbrowser=TRACE
13-
IOS_TARGET: x86_64-apple-ios
13+
IOS_TARGET: aarch64-apple-ios-sim
1414

1515
jobs:
1616
build:
@@ -24,6 +24,8 @@ jobs:
2424
steps:
2525
- uses: actions/checkout@v3
2626
name: Checkout
27+
- name: Select Xcode 15.4
28+
run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer
2729
- name: Install rust version
2830
run: |
2931
rustup install ${{ matrix.rust }} --profile minimal
@@ -33,19 +35,19 @@ jobs:
3335
- name: Configure and start iOS Simulator
3436
run: |
3537
set -e
36-
open -a Simulator
37-
sleep 5
38-
IOSRUNTIME=$(xcrun simctl list 2>&1 | egrep '^iOS' | head -n 1 | awk '{ print $NF }')
38+
IOSRUNTIME=com.apple.CoreSimulator.SimRuntime.iOS-17-5
3939
IOSDEV=$(xcrun simctl list 2>&1 | grep com.apple.CoreSimulator.SimDeviceType.iPhone | grep -v ' SE ' | tail -n 1 | tr -d '()' | awk '{ print $NF }')
4040
DEVID=$(xcrun simctl create iphone-latest $IOSDEV $IOSRUNTIME)
4141
echo "==== using device $IOSDEV, $IOSRUNTIME ===="
4242
xcrun simctl boot $DEVID
43-
sleep 5
43+
sleep 10
4444
xcrun simctl list 2>&1
4545
4646
# Run tests
4747
- name: Run tests
48-
run: cargo +${{ matrix.rust }} test --verbose --test test_ios -- --include-ignored
48+
run: cargo +${{ matrix.rust }} test --verbose --test test_ios -- --include-ignored --nocapture
49+
env:
50+
TEST_REQ_TIMEOUT: '300'
4951

5052
# Code format, linting etc.
5153
- name: Check Code Formatting

Cargo.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ core-foundation = "0.9"
3535
jni = "0.21"
3636
ndk-context = "0.1"
3737

38-
[target.'cfg(target_os = "ios")'.dependencies]
39-
raw-window-handle = "0.5.0"
40-
objc = "0.2.7"
38+
[target.'cfg(any(target_os = "ios", target_os = "tvos", target_os = "visionos"))'.dependencies]
39+
block2 = "0.5.0"
40+
objc2 = "0.5.1"
41+
objc2-foundation = { version = "0.2.0", features = [
42+
"NSDictionary",
43+
"NSString",
44+
"NSURL",
45+
] }
4146

4247
[dev-dependencies]
4348
actix-web = "4"

src/ios.rs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType};
2-
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
2+
use block2::Block;
3+
use objc2::rc::Id;
4+
use objc2::runtime::Bool;
5+
use objc2::{class, msg_send, msg_send_id};
6+
use objc2_foundation::{NSDictionary, NSObject, NSString, NSURL};
37

4-
/// Deal with opening of browsers on iOS
8+
fn app() -> Option<Id<NSObject>> {
9+
unsafe { msg_send_id![class!(UIApplication), sharedApplication] }
10+
}
11+
12+
fn open_url(
13+
app: &NSObject,
14+
url: &NSURL,
15+
options: &NSDictionary,
16+
handler: Option<&Block<dyn Fn(Bool)>>,
17+
) {
18+
unsafe { msg_send![app, openURL: url, options: options, completionHandler: handler] }
19+
}
20+
21+
/// Deal with opening of browsers on iOS/tvOS/visionOS.
22+
///
23+
/// watchOS doesn't have a browser, so this won't work there.
524
pub(super) fn open_browser_internal(
625
_browser: Browser,
726
target: &TargetType,
@@ -15,28 +34,22 @@ pub(super) fn open_browser_internal(
1534
return Ok(());
1635
}
1736

18-
unsafe {
19-
let app: *mut Object = msg_send![class!(UIApplication), sharedApplication];
20-
if app.is_null() {
21-
return Err(Error::new(
22-
ErrorKind::Other,
23-
"UIApplication is null, can't open url",
24-
));
25-
}
26-
27-
let url_cstr = std::ffi::CString::new(url)?;
37+
let app = app().ok_or(Error::new(
38+
ErrorKind::Other,
39+
"UIApplication is null, can't open url",
40+
))?;
2841

29-
// Create ns string class from our string
30-
let url_string: *mut Object = msg_send![class!(NSString), stringWithUTF8String: url_cstr];
31-
// Create NSURL object with given string
32-
let url_object: *mut Object = msg_send![class!(NSURL), URLWithString: url_string];
33-
// No completion handler
34-
let nil: *mut Object = ::core::ptr::null_mut();
35-
// empty options dictionary
36-
let no_options: *mut Object = msg_send![class!(NSDictionary), new];
42+
// Create ns string class from our string
43+
let url_string = NSString::from_str(url);
44+
// Create NSURL object with given string
45+
let url_object = unsafe { NSURL::URLWithString(&url_string) }.ok_or(Error::new(
46+
ErrorKind::Other,
47+
"Failed creating NSURL; is the URL valid?",
48+
))?;
49+
// empty options dictionary
50+
let options = NSDictionary::new();
3751

38-
// Open url
39-
let () = msg_send![app, openURL:url_object options:no_options completionHandler:nil];
40-
Ok(())
41-
}
52+
// Open url
53+
open_url(&app, &url_object, &options, None);
54+
Ok(())
4255
}

src/lib.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
//!
1515
//! ## Platform Support Status
1616
//!
17-
//! | Platform | Supported | Browsers | Test status |
18-
//! |----------|-----------|----------|-------------|
19-
//! | macos | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ |
20-
//! | windows | ✅ | default only | ✅ |
21-
//! | linux/wsl | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ |
22-
//! | android | ✅ | default only | ✅ |
23-
//! | ios | ✅ | default only | ✅ |
24-
//! | wasm | ✅ | default only | ✅ |
17+
//! | Platform | Supported | Browsers | Test status |
18+
//! |-----------------------|-----------|----------|-------------|
19+
//! | macOS | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ |
20+
//! | windows | ✅ | default only | ✅ |
21+
//! | linux/wsl | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ |
22+
//! | android | ✅ | default only | ✅ |
23+
//! | iOS/tvOS/visionOS | ✅ | default only | ✅ |
24+
//! | wasm | ✅ | default only | ✅ |
2525
//! | unix (*bsd, aix etc.) | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | Manual |
2626
//!
2727
//! ## Consistent Behaviour
@@ -39,7 +39,10 @@
3939
//! * `disable-wsl` - this disables WSL `file` implementation (`http` still works)
4040
//! * `wasm-console` - this enables logging to wasm console (valid only on wasm platform)
4141
42-
#[cfg_attr(any(target_os = "ios", target_os = "tvos"), path = "ios.rs")]
42+
#[cfg_attr(
43+
any(target_os = "ios", target_os = "tvos", target_os = "visionos"),
44+
path = "ios.rs"
45+
)]
4346
#[cfg_attr(target_os = "macos", path = "macos.rs")]
4447
#[cfg_attr(target_os = "android", path = "android.rs")]
4548
#[cfg_attr(target_family = "wasm", path = "wasm.rs")]
@@ -50,6 +53,7 @@
5053
not(any(
5154
target_os = "ios",
5255
target_os = "tvos",
56+
target_os = "visionos",
5357
target_os = "macos",
5458
target_os = "android",
5559
target_family = "wasm",
@@ -67,6 +71,7 @@ mod os;
6771
not(any(
6872
target_os = "ios",
6973
target_os = "tvos",
74+
target_os = "visionos",
7075
target_os = "macos",
7176
target_os = "android",
7277
target_family = "wasm",
@@ -316,6 +321,7 @@ pub fn open_browser_with_options(
316321
if cfg!(any(
317322
target_os = "ios",
318323
target_os = "tvos",
324+
target_os = "visionos",
319325
target_os = "macos",
320326
target_os = "android",
321327
target_family = "wasm",
@@ -338,6 +344,8 @@ impl TargetType {
338344
feature = "hardened",
339345
target_os = "android",
340346
target_os = "ios",
347+
target_os = "tvos",
348+
target_os = "visionos",
341349
target_family = "wasm"
342350
))]
343351
fn is_http(&self) -> bool {
@@ -346,7 +354,13 @@ impl TargetType {
346354

347355
/// If `target` represents a valid http/https url, return the str corresponding to it
348356
/// else return `std::io::Error` of kind `std::io::ErrorKind::InvalidInput`
349-
#[cfg(any(target_os = "android", target_os = "ios", target_family = "wasm"))]
357+
#[cfg(any(
358+
target_os = "android",
359+
target_os = "ios",
360+
target_os = "tvos",
361+
target_os = "visionos",
362+
target_family = "wasm"
363+
))]
350364
fn get_http_url(&self) -> Result<&str> {
351365
if self.is_http() {
352366
Ok(self.0.as_str())

tests/common.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ where
8181
op(&format!("http://{}:{}{}", host, port, &uri), port);
8282

8383
// wait for the url to be hit
84-
let timeout = 90;
84+
let timeout = option_env!("TEST_REQ_TIMEOUT")
85+
.map(|s| s.parse().expect("failed to parse TEST_REQ_TIMEOUT"))
86+
.unwrap_or(90);
8587
match rx.recv_timeout(std::time::Duration::from_secs(timeout)) {
8688
Ok(msg) => assert_eq!(decode(&msg).unwrap(), uri),
8789
Err(_) => panic!("failed to receive uri data"),

tests/test_ios.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ mod tests {
2929
glue_dir.push("testglue");
3030
run_cmd(&glue_dir, &["./build"]).expect("glue code build failed");
3131

32+
let compile_app = || {
33+
run_cmd(
34+
&app_dir,
35+
&[
36+
"xcrun",
37+
"xcodebuild",
38+
"-project",
39+
"test-ios-app.xcodeproj",
40+
"-configuration",
41+
"Debug",
42+
"-sdk",
43+
"iphonesimulator",
44+
"-destination",
45+
"platform=iOS Simulator,name=iphone-latest",
46+
"-arch",
47+
if cfg!(target_arch = "aarch64") {
48+
"arm64"
49+
} else {
50+
"x86_64"
51+
},
52+
],
53+
)
54+
};
55+
compile_app().expect("compilation warm up failed for the app");
56+
3257
// invoke server
3358
check_request_received_using(uri, &ipv4, |url, _port| {
3459
// modify ios app code to use the correct url
@@ -47,8 +72,9 @@ mod tests {
4772
})
4873
.collect::<Vec<String>>()
4974
.join("\n");
50-
fs::write(&swift_src, new_code).expect("failed to modify ContentView.swift");
75+
fs::write(&swift_src, &new_code).expect("failed to modify ContentView.swift");
5176
let revert_code = || fs::write(&swift_src, &old_code).expect("failed to revert code");
77+
println!("Modifying ContentView.swift to:\n{}", &new_code);
5278
let handle_exec_result = |result: std::io::Result<ExitStatus>, err_msg: &str| {
5379
revert_code();
5480
let success = match result {
@@ -62,27 +88,7 @@ mod tests {
6288
};
6389

6490
// build app
65-
let exec_result = run_cmd(
66-
&app_dir,
67-
&[
68-
"xcrun",
69-
"xcodebuild",
70-
"-project",
71-
"test-ios-app.xcodeproj",
72-
"-configuration",
73-
"Debug",
74-
"-sdk",
75-
"iphonesimulator",
76-
"-destination",
77-
"platform=iOS Simulator,name=iphone-latest",
78-
"-arch",
79-
if cfg!(target_arch = "aarch64") {
80-
"arm64"
81-
} else {
82-
"x86_64"
83-
},
84-
],
85-
);
91+
let exec_result = compile_app();
8692
handle_exec_result(exec_result, "failed to build ios app");
8793

8894
// launch app on simulator

tests/test_macos.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ mod tests {
1818
check_browser(Browser::Safari, TEST_PLATFORM).await;
1919
}
2020

21-
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
22-
#[ignore]
23-
async fn test_open_firefox() {
24-
check_browser(Browser::Firefox, TEST_PLATFORM).await;
25-
}
21+
// #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
22+
// #[ignore]
23+
// async fn test_open_firefox() {
24+
// check_browser(Browser::Firefox, TEST_PLATFORM).await;
25+
// }
2626

2727
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2828
#[ignore]

0 commit comments

Comments
 (0)