fix(blob): preserve receiver realm for slice results

This commit is contained in:
ldm0
2026-09-26 05:52:44 +08:00
committed by Donough Liu
parent f846fcb7f2
commit 5d740ca1bd
2 changed files with 107 additions and 9 deletions
+43 -9
View File
@@ -15,6 +15,7 @@ use super::{
const BLOB_ID_SLOT: &str = "__lmBlobId";
const BLOB_PROTOTYPE_SLOT: &str = "__lmBlobPrototype";
const BLOB_REALM_PROTOTYPE_SLOT: &str = "__lmBlobRealmPrototype";
fn native_blob_line_ending() -> &'static str {
if moli_browser_profile::DEFAULT_WINDOW_SURFACE_PROFILE
@@ -225,7 +226,11 @@ pub(super) fn blob_slice_callback<'s>(
.map(ToOwned::to_owned)
.unwrap_or_default();
if let Some(blob) = build_blob_object(scope, sliced, mime_type) {
let prototype = blob_realm_prototype_from_object(scope, args.this())
.or_else(|| current_realm_blob_prototype(scope));
if let Some(blob) = prototype
.and_then(|prototype| build_blob_object_with_prototype(scope, sliced, mime_type, prototype))
{
rv.set(blob.into());
} else {
rv.set(v8::undefined(scope).into());
@@ -244,6 +249,9 @@ pub(super) fn init_blob_object<'s>(
BlobInstanceDeclaration::new(v8::BigInt::new_from_u64(scope, blob_id))
.initialize(scope, object)
.expect("Blob instance declaration should initialize");
if let Some(prototype) = current_realm_blob_prototype(scope) {
set_private_value(scope, object, BLOB_REALM_PROTOTYPE_SLOT, prototype.into());
}
track_blob_ref_lifetime(scope, object, move || release_blob_wrapper_ref(blob_id));
blob_id
}
@@ -252,15 +260,40 @@ pub(super) fn build_blob_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
bytes: Vec<u8>,
mime_type: String,
) -> Option<v8::Local<'s, v8::Object>> {
let prototype = current_realm_blob_prototype(scope)?;
build_blob_object_with_prototype(scope, bytes, mime_type, prototype)
}
fn current_realm_blob_prototype<'s>(
scope: &mut v8::PinScope<'s, '_>,
) -> Option<v8::Local<'s, v8::Object>> {
let global = scope.get_current_context().global(scope);
let prototype = get_private_value(scope, global, BLOB_PROTOTYPE_SLOT).or_else(|| {
let prototype =
crate::context_bootstrap::ensure_intrinsic_interface_prototype(scope, "Blob").ok()?;
finalize_blob_realm_bindings(scope, prototype);
Some(prototype.into())
});
let prototype = prototype?;
get_private_value(scope, global, BLOB_PROTOTYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
.or_else(|| {
let prototype =
crate::context_bootstrap::ensure_intrinsic_interface_prototype(scope, "Blob")
.ok()?;
finalize_blob_realm_bindings(scope, prototype);
Some(prototype)
})
}
fn blob_realm_prototype_from_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> Option<v8::Local<'s, v8::Object>> {
get_private_value(scope, object, BLOB_REALM_PROTOTYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
}
fn build_blob_object_with_prototype<'s>(
scope: &mut v8::PinScope<'s, '_>,
bytes: Vec<u8>,
mime_type: String,
prototype: v8::Local<'s, v8::Object>,
) -> Option<v8::Local<'s, v8::Object>> {
let owner_id = current_resource_owner_id(scope);
let partition_id = current_blob_storage_partition_identity(scope);
let blob_id = blob_store().create_blob(owner_id, partition_id, bytes, mime_type);
@@ -271,7 +304,8 @@ pub(super) fn build_blob_object<'s>(
release_blob_wrapper_ref(blob_id);
None
})?;
let _ = object.set_prototype(scope, prototype);
let _ = object.set_prototype(scope, prototype.into());
set_private_value(scope, object, BLOB_REALM_PROTOTYPE_SLOT, prototype.into());
track_blob_ref_lifetime(scope, object, move || release_blob_wrapper_ref(blob_id));
Some(object)
}
@@ -3129,6 +3129,70 @@ fn blob_internal_builders_survive_global_blob_override() {
);
}
#[test]
fn blob_slice_uses_receiver_realm_after_method_realm_is_detached() {
let mut vm = new_parsed_test_vm(
"https://blob-slice-receiver-realm.test/",
"<!doctype html><html><body></body></html>",
);
vm.eval(
r#"
(() => {
const iframe = document.createElement("iframe");
iframe.srcdoc = "<!doctype html><html><body></body></html>";
document.body.appendChild(iframe);
globalThis.__blobSliceRealmFrame = iframe;
})()
"#,
)
.expect("detached-Realm Blob slice setup should evaluate");
vm.drain_pending_child_frame_work_for_test();
vm.eval(
r#"
(() => {
const iframe = __blobSliceRealmFrame;
const detachedSlice = iframe.contentWindow.Blob.prototype.slice;
iframe.remove();
const blobSlice = detachedSlice.call(new Blob(["abcdef"]), 1, 4, "Text/Custom");
const fileSlice = detachedSlice.call(new File(["uvwxyz"], "sample.txt"), 2, 5);
globalThis.__blobSliceRealmProbe = {
childDetached: iframe.contentWindow === null,
blobIsMainRealmBlob: blobSlice instanceof Blob,
blobPrototypeIsMainRealmBlob: Object.getPrototypeOf(blobSlice) === Blob.prototype,
fileSliceIsMainRealmBlob: fileSlice instanceof Blob,
fileSliceIsFile: fileSlice instanceof File,
fileSlicePrototypeIsMainRealmBlob: Object.getPrototypeOf(fileSlice) === Blob.prototype,
type: blobSlice.type
};
Promise.all([blobSlice.text(), fileSlice.text()]).then(
([blobText, fileText]) => {
__blobSliceRealmProbe.blobText = blobText;
__blobSliceRealmProbe.fileText = fileText;
},
error => { __blobSliceRealmProbe.error = error && error.name; }
);
return "scheduled";
})()
"#,
)
.expect("detached-Realm Blob slice probe should evaluate");
vm.eval("0")
.expect("detached-Realm Blob slice promise microtasks should drain");
let result = vm
.eval("JSON.stringify(globalThis.__blobSliceRealmProbe)")
.expect("detached-Realm Blob slice result should evaluate");
assert_eq!(
result,
r#"{"childDetached":true,"blobIsMainRealmBlob":true,"blobPrototypeIsMainRealmBlob":true,"fileSliceIsMainRealmBlob":true,"fileSliceIsFile":false,"fileSlicePrototypeIsMainRealmBlob":true,"type":"text/custom","blobText":"bcd","fileText":"wxy"}"#
);
}
#[test]
fn blob_stream_is_native_readable_stream_and_response_consumes_bytes() {
let mut vm = new_storage_test_vm("https://blob-stream-reader.test/");