Skip to content

Commit 2edf3f9

Browse files
committed
perf(macOS): avoid copying large protocol bodies
1 parent 44e26ef commit 2edf3f9

4 files changed

Lines changed: 29 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wry": patch
3+
---
4+
5+
On macOS, avoid an extra copy for owned custom protocol response bodies of 128KB or larger by transferring the body buffer into `NSData`.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ objc2-core-foundation = { version = "0.3.0", default-features = false, features
138138
] }
139139
objc2-foundation = { version = "0.3.0", default-features = false, features = [
140140
"std",
141+
"block2",
141142
"objc2-core-foundation",
142143
"NSURLRequest",
143144
"NSURL",

src/wkwebview/class/url_scheme_handler.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use objc2_web_kit::{WKURLSchemeHandler, WKURLSchemeTask};
2626

2727
use crate::{wkwebview::WEBVIEW_STATE, RequestAsyncResponder, WryWebView};
2828

29+
const NO_COPY_DATA_THRESHOLD: usize = 128 * 1024;
30+
2931
pub fn create(name: &str) -> &AnyClass {
3032
unsafe {
3133
// Include the address of WEBVIEW_STATE in the class name so that each dylib in the process
@@ -215,6 +217,7 @@ extern "C" fn start_task(
215217
check_task_is_valid(&webview, task_key, task_uuid.clone())?;
216218

217219
let content = sent_response.body();
220+
let content_len = content.len();
218221
// default: application/octet-stream, but should be provided by the client
219222
let wanted_mime = sent_response.headers().get(CONTENT_TYPE);
220223
// default to 200
@@ -231,7 +234,7 @@ extern "C" fn start_task(
231234
}
232235
headers.insert(
233236
&*NSString::from_str(CONTENT_LENGTH.as_str()),
234-
&*NSString::from_str(&content.len().to_string()),
237+
&*NSString::from_str(&content_len.to_string()),
235238
);
236239

237240
// add headers
@@ -264,15 +267,24 @@ extern "C" fn start_task(
264267
}))
265268
.map_err(|_e| crate::Error::CustomProtocolTaskInvalid)?;
266269

267-
// Send data
268-
let data = NSData::alloc();
269-
// MIGRATE NOTE: we copied the content to the NSData because content will be freed
270-
// when out of scope but NSData will also free the content when it's done and cause doube free.
271-
let data = NSData::initWithBytes_length(
272-
data,
273-
content.as_ptr() as *mut c_void,
274-
content.len(),
275-
);
270+
let data = if content_len < NO_COPY_DATA_THRESHOLD {
271+
let data = NSData::alloc();
272+
// Keep small responses on the original copy path; no-copy deallocation costs more.
273+
NSData::initWithBytes_length(data, content.as_ptr() as *mut c_void, content.len())
274+
} else {
275+
match sent_response.into_body() {
276+
Cow::Owned(content) => NSData::from_vec(content),
277+
Cow::Borrowed(content) => {
278+
let data = NSData::alloc();
279+
// Copy borrowed responses because NSData cannot take ownership.
280+
NSData::initWithBytes_length(
281+
data,
282+
content.as_ptr() as *mut c_void,
283+
content.len(),
284+
)
285+
}
286+
}
287+
};
276288

277289
// Check validity again
278290
check_webview_id_valid(webview_id)?;

0 commit comments

Comments
 (0)