diff --git a/core/engine/src/tests/async_generator.rs b/core/engine/src/tests/async_generator.rs index a263d555272..188ddcc325f 100644 --- a/core/engine/src/tests/async_generator.rs +++ b/core/engine/src/tests/async_generator.rs @@ -1,5 +1,5 @@ use crate::{ - Context, JsValue, TestAction, builtins::promise::PromiseState, object::JsPromise, + Context, JsValue, Source, TestAction, builtins::promise::PromiseState, object::JsPromise, run_test_actions, }; use boa_macros::js_str; @@ -120,3 +120,77 @@ fn return_on_then_queue() { TestAction::assert_eq("count", JsValue::from(2)), ]); } + +#[test] +fn cross_realm_async_generator_yield() { + // Exercises AsyncGeneratorYield spec steps 6-8 (previousRealm handling) + // by creating a generator in one realm and consuming it from another. + // Per spec, previousRealm is the realm of the second-to-top execution + // context (the `next()` / AwaitFulfilled handler), which has the same + // realm as the generator. The iter result prototype should match the + // generator realm's Object.prototype. + let mut context = Context::default(); + + let generator_realm = context.create_realm().unwrap(); + + let old_realm = context.enter_realm(generator_realm.clone()); + let generator = context + .eval(Source::from_bytes( + b"(async function* g() { yield 42; yield 99; })()", + )) + .unwrap(); + context.enter_realm(old_realm); + + // Grab Object.prototype from the generator's realm (previousRealm per spec). + let gen_realm_object_proto = generator_realm + .intrinsics() + .constructors() + .object() + .prototype(); + + let next_fn = generator + .as_object() + .unwrap() + .get(js_str!("next"), &mut context) + .unwrap(); + + let call_next = |ctx: &mut Context| -> JsValue { + let result = next_fn + .as_callable() + .unwrap() + .call(&generator, &[], ctx) + .unwrap(); + ctx.run_jobs().unwrap(); + result + }; + + // First yield: value 42 + let first = call_next(&mut context); + assert_promise_iter_value(&first, &JsValue::from(42), false, &mut context); + + // Verify the iter result was created in the generator's realm (previousRealm). + let first_promise = JsPromise::from_object(first.as_object().unwrap().clone()).unwrap(); + let PromiseState::Fulfilled(first_result) = first_promise.state() else { + panic!("promise was not fulfilled"); + }; + assert_eq!( + first_result.as_object().unwrap().prototype(), + Some(gen_realm_object_proto.clone()), + "iter result prototype should be generator realm's Object.prototype" + ); + + // Second yield: value 99 + let second = call_next(&mut context); + assert_promise_iter_value(&second, &JsValue::from(99), false, &mut context); + + // Verify the iter result was created in the generator's realm (previousRealm). + let second_promise = JsPromise::from_object(second.as_object().unwrap().clone()).unwrap(); + let PromiseState::Fulfilled(second_result) = second_promise.state() else { + panic!("promise was not fulfilled"); + }; + assert_eq!( + second_result.as_object().unwrap().prototype(), + Some(gen_realm_object_proto), + "iter result prototype should be generator realm's Object.prototype" + ); +} diff --git a/core/engine/src/vm/opcode/generator/yield_stm.rs b/core/engine/src/vm/opcode/generator/yield_stm.rs index 8e91aa3fbdf..a3c25419aac 100644 --- a/core/engine/src/vm/opcode/generator/yield_stm.rs +++ b/core/engine/src/vm/opcode/generator/yield_stm.rs @@ -66,13 +66,23 @@ impl AsyncGeneratorYield { let value = context.vm.get_register(value.into()); let completion = Ok(value.clone()); - // TODO: 6. Assert: The execution context stack has at least two elements. - // TODO: 7. Let previousContext be the second to top element of the execution context stack. - // TODO: 8. Let previousRealm be previousContext's Realm. + // 6. Assert: The execution context stack has at least two elements. + // 7. Let previousContext be the second to top element of the execution context stack. + // 8. Let previousRealm be previousContext's Realm. + let previous_realm = context + .vm + .frames + .get(context.vm.frames.len() - 2) + .map(|frame| frame.realm.clone()); + // 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm). - if let Err(err) = - AsyncGenerator::complete_step(&async_generator_object, completion, false, None, context) - { + if let Err(err) = AsyncGenerator::complete_step( + &async_generator_object, + completion, + false, + previous_realm, + context, + ) { return context.handle_error(err); } @@ -114,8 +124,9 @@ impl AsyncGeneratorYield { // a. Set generator.[[AsyncGeneratorState]] to suspended-yield. r#gen.data_mut().state = AsyncGeneratorState::SuspendedYield; - // TODO: b. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. - // TODO: c. Let callerContext be the running execution context. + // b. Remove genContext from the execution context stack and restore the execution context + // that is at the top of the execution context stack as the running execution context. + // c. Let callerContext be the running execution context. // d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed. // e. Assert: If control reaches here, then genContext is the running execution context again. // f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).