Skip to content

Commit

Permalink
Move to objc2 instead of objc. See PR #87
Browse files Browse the repository at this point in the history
  • Loading branch information
amodm committed May 4, 2024
2 parents a16d6ae + 9184245 commit e81d11c
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/android.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ env:
jobs:
build:
name: Build
runs-on: macos-latest
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-android') }}
strategy:
matrix:
Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/ios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ on:

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUST_BACKTRACE: full
RUST_LOG: webbrowser=TRACE
IOS_TARGET: x86_64-apple-ios
IOS_TARGET: aarch64-apple-ios-sim

jobs:
build:
Expand All @@ -24,6 +24,8 @@ jobs:
steps:
- uses: actions/checkout@v3
name: Checkout
- name: Select Xcode 15.4
run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer
- name: Install rust version
run: |
rustup install ${{ matrix.rust }} --profile minimal
Expand All @@ -33,19 +35,19 @@ jobs:
- name: Configure and start iOS Simulator
run: |
set -e
open -a Simulator
sleep 5
IOSRUNTIME=$(xcrun simctl list 2>&1 | egrep '^iOS' | head -n 1 | awk '{ print $NF }')
IOSRUNTIME=com.apple.CoreSimulator.SimRuntime.iOS-17-5
IOSDEV=$(xcrun simctl list 2>&1 | grep com.apple.CoreSimulator.SimDeviceType.iPhone | grep -v ' SE ' | tail -n 1 | tr -d '()' | awk '{ print $NF }')
DEVID=$(xcrun simctl create iphone-latest $IOSDEV $IOSRUNTIME)
echo "==== using device $IOSDEV, $IOSRUNTIME ===="
xcrun simctl boot $DEVID
sleep 5
sleep 10
xcrun simctl list 2>&1
# Run tests
- name: Run tests
run: cargo +${{ matrix.rust }} test --verbose --test test_ios -- --include-ignored
run: cargo +${{ matrix.rust }} test --verbose --test test_ios -- --include-ignored --nocapture
env:
TEST_REQ_TIMEOUT: '300'

# Code format, linting etc.
- name: Check Code Formatting
Expand Down
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ core-foundation = "0.9"
jni = "0.21"
ndk-context = "0.1"

[target.'cfg(target_os = "ios")'.dependencies]
raw-window-handle = "0.5.0"
objc = "0.2.7"
[target.'cfg(any(target_os = "ios", target_os = "tvos", target_os = "visionos"))'.dependencies]
block2 = "0.5.0"
objc2 = "0.5.1"
objc2-foundation = { version = "0.2.0", features = [
"NSDictionary",
"NSString",
"NSURL",
] }

[dev-dependencies]
actix-web = "4"
Expand Down
61 changes: 37 additions & 24 deletions src/ios.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType};
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use block2::Block;
use objc2::rc::Id;
use objc2::runtime::Bool;
use objc2::{class, msg_send, msg_send_id};
use objc2_foundation::{NSDictionary, NSObject, NSString, NSURL};

/// Deal with opening of browsers on iOS
fn app() -> Option<Id<NSObject>> {
unsafe { msg_send_id![class!(UIApplication), sharedApplication] }
}

fn open_url(
app: &NSObject,
url: &NSURL,
options: &NSDictionary,
handler: Option<&Block<dyn Fn(Bool)>>,
) {
unsafe { msg_send![app, openURL: url, options: options, completionHandler: handler] }
}

/// Deal with opening of browsers on iOS/tvOS/visionOS.
///
/// watchOS doesn't have a browser, so this won't work there.
pub(super) fn open_browser_internal(
_browser: Browser,
target: &TargetType,
Expand All @@ -15,28 +34,22 @@ pub(super) fn open_browser_internal(
return Ok(());
}

unsafe {
let app: *mut Object = msg_send![class!(UIApplication), sharedApplication];
if app.is_null() {
return Err(Error::new(
ErrorKind::Other,
"UIApplication is null, can't open url",
));
}

let url_cstr = std::ffi::CString::new(url)?;
let app = app().ok_or(Error::new(
ErrorKind::Other,
"UIApplication is null, can't open url",
))?;

// Create ns string class from our string
let url_string: *mut Object = msg_send![class!(NSString), stringWithUTF8String: url_cstr];
// Create NSURL object with given string
let url_object: *mut Object = msg_send![class!(NSURL), URLWithString: url_string];
// No completion handler
let nil: *mut Object = ::core::ptr::null_mut();
// empty options dictionary
let no_options: *mut Object = msg_send![class!(NSDictionary), new];
// Create ns string class from our string
let url_string = NSString::from_str(url);
// Create NSURL object with given string
let url_object = unsafe { NSURL::URLWithString(&url_string) }.ok_or(Error::new(
ErrorKind::Other,
"Failed creating NSURL; is the URL valid?",
))?;
// empty options dictionary
let options = NSDictionary::new();

// Open url
let () = msg_send![app, openURL:url_object options:no_options completionHandler:nil];
Ok(())
}
// Open url
open_url(&app, &url_object, &options, None);
Ok(())
}
34 changes: 24 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
//!
//! ## Platform Support Status
//!
//! | Platform | Supported | Browsers | Test status |
//! |----------|-----------|----------|-------------|
//! | macos | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ |
//! | windows | ✅ | default only | ✅ |
//! | linux/wsl | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ |
//! | android | ✅ | default only | ✅ |
//! | ios | ✅ | default only | ✅ |
//! | wasm | ✅ | default only | ✅ |
//! | Platform | Supported | Browsers | Test status |
//! |-----------------------|-----------|----------|-------------|
//! | macOS | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ |
//! | windows | ✅ | default only | ✅ |
//! | linux/wsl | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ |
//! | android | ✅ | default only | ✅ |
//! | iOS/tvOS/visionOS | ✅ | default only | ✅ |
//! | wasm | ✅ | default only | ✅ |
//! | unix (*bsd, aix etc.) | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | Manual |
//!
//! ## Consistent Behaviour
Expand All @@ -39,7 +39,10 @@
//! * `disable-wsl` - this disables WSL `file` implementation (`http` still works)
//! * `wasm-console` - this enables logging to wasm console (valid only on wasm platform)

#[cfg_attr(any(target_os = "ios", target_os = "tvos"), path = "ios.rs")]
#[cfg_attr(
any(target_os = "ios", target_os = "tvos", target_os = "visionos"),
path = "ios.rs"
)]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
#[cfg_attr(target_os = "android", path = "android.rs")]
#[cfg_attr(target_family = "wasm", path = "wasm.rs")]
Expand All @@ -50,6 +53,7 @@
not(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
Expand All @@ -67,6 +71,7 @@ mod os;
not(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
Expand Down Expand Up @@ -316,6 +321,7 @@ pub fn open_browser_with_options(
if cfg!(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
Expand All @@ -338,6 +344,8 @@ impl TargetType {
feature = "hardened",
target_os = "android",
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_family = "wasm"
))]
fn is_http(&self) -> bool {
Expand All @@ -346,7 +354,13 @@ impl TargetType {

/// If `target` represents a valid http/https url, return the str corresponding to it
/// else return `std::io::Error` of kind `std::io::ErrorKind::InvalidInput`
#[cfg(any(target_os = "android", target_os = "ios", target_family = "wasm"))]
#[cfg(any(
target_os = "android",
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_family = "wasm"
))]
fn get_http_url(&self) -> Result<&str> {
if self.is_http() {
Ok(self.0.as_str())
Expand Down
4 changes: 3 additions & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ where
op(&format!("http://{}:{}{}", host, port, &uri), port);

// wait for the url to be hit
let timeout = 90;
let timeout = option_env!("TEST_REQ_TIMEOUT")
.map(|s| s.parse().expect("failed to parse TEST_REQ_TIMEOUT"))
.unwrap_or(90);
match rx.recv_timeout(std::time::Duration::from_secs(timeout)) {
Ok(msg) => assert_eq!(decode(&msg).unwrap(), uri),
Err(_) => panic!("failed to receive uri data"),
Expand Down
50 changes: 28 additions & 22 deletions tests/test_ios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ mod tests {
glue_dir.push("testglue");
run_cmd(&glue_dir, &["./build"]).expect("glue code build failed");

let compile_app = || {
run_cmd(
&app_dir,
&[
"xcrun",
"xcodebuild",
"-project",
"test-ios-app.xcodeproj",
"-configuration",
"Debug",
"-sdk",
"iphonesimulator",
"-destination",
"platform=iOS Simulator,name=iphone-latest",
"-arch",
if cfg!(target_arch = "aarch64") {
"arm64"
} else {
"x86_64"
},
],
)
};
compile_app().expect("compilation warm up failed for the app");

// invoke server
check_request_received_using(uri, &ipv4, |url, _port| {
// modify ios app code to use the correct url
Expand All @@ -47,8 +72,9 @@ mod tests {
})
.collect::<Vec<String>>()
.join("\n");
fs::write(&swift_src, new_code).expect("failed to modify ContentView.swift");
fs::write(&swift_src, &new_code).expect("failed to modify ContentView.swift");
let revert_code = || fs::write(&swift_src, &old_code).expect("failed to revert code");
println!("Modifying ContentView.swift to:\n{}", &new_code);
let handle_exec_result = |result: std::io::Result<ExitStatus>, err_msg: &str| {
revert_code();
let success = match result {
Expand All @@ -62,27 +88,7 @@ mod tests {
};

// build app
let exec_result = run_cmd(
&app_dir,
&[
"xcrun",
"xcodebuild",
"-project",
"test-ios-app.xcodeproj",
"-configuration",
"Debug",
"-sdk",
"iphonesimulator",
"-destination",
"platform=iOS Simulator,name=iphone-latest",
"-arch",
if cfg!(target_arch = "aarch64") {
"arm64"
} else {
"x86_64"
},
],
);
let exec_result = compile_app();
handle_exec_result(exec_result, "failed to build ios app");

// launch app on simulator
Expand Down
10 changes: 5 additions & 5 deletions tests/test_macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ mod tests {
check_browser(Browser::Safari, TEST_PLATFORM).await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore]
async fn test_open_firefox() {
check_browser(Browser::Firefox, TEST_PLATFORM).await;
}
// #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
// #[ignore]
// async fn test_open_firefox() {
// check_browser(Browser::Firefox, TEST_PLATFORM).await;
// }

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

0 comments on commit e81d11c

Please sign in to comment.