Commit 7558e182 authored by Francis McCabe's avatar Francis McCabe Committed by Commit Bot

Revert "Use context of then function for PromiseResolveThenableJob"

This reverts commit 93253978.

Reason for revert: Causing blink layout failures. See 

https://ci.chromium.org/p/v8/builders/ci/V8%20Blink%20Linux%20Future/2684

Original change's description:
> Use context of then function for PromiseResolveThenableJob
> 
> When a microtask is executed, we need to use an appropriate,
> non-detached Context for its execution. Currently with
> PromiseResolveThenableJobs [1], the Context used is always drawn from
> the realm of the Promise constructor being used. This may cause
> non-intuitive behavior, such as in the following case:
> 
>   const DeadPromise = iframe.contentWindow.Promise;
>   const p = DeadPromise.resolve({
>     then() {
>       return { success: true };
>     }
>   });
>   p.then(result => { console.log(result); });
> 
>   // Some time later, but synchronously...
>   iframe.src = "http://example.com"; // navigate away.
>   // DeadPromise's Context is detached state now.
>   // p never gets resolved, and its reaction handler never gets called.
> 
> To fix this behavior, when PromiseResolveThenableJob is being queued up,
> the `then` method of the thenable should be used to determine the
> context of the resultant microtask. Doing so aligns with Firefox, and
> also with the latest HTML spec [2][3].
> 
> This change is analogous to CL 1465902, which uses the realm of the
> reaction handlers to determine the Context PromiseReactionJobs run in.
> 
> [1]: https://tc39.es/ecma262/#sec-promiseresolvethenablejob
> [2]: https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
> [3]: https://github.com/whatwg/html/pull/5212
> 
> Bug: v8:10200
> Change-Id: I2312788eeea0f9e870c13cf3cb5730a87d15609e
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2071624
> Commit-Queue: Timothy Gu <timothygu@chromium.org>
> Reviewed-by: Toon Verwaest <verwaest@chromium.org>
> Reviewed-by: Shu-yu Guo <syg@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#66507}

TBR=verwaest@chromium.org,timothygu@chromium.org,syg@chromium.org

Change-Id: I81737750f8b369567ba586c5a2cfb489836b7e74
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:10200
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2081091Reviewed-by: 's avatarFrancis McCabe <fgm@chromium.org>
Commit-Queue: Francis McCabe <fgm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66510}
parent 2b4dd779
...@@ -40,8 +40,7 @@ namespace promise { ...@@ -40,8 +40,7 @@ namespace promise {
EnqueueMicrotask(Context, Microtask): Undefined; EnqueueMicrotask(Context, Microtask): Undefined;
macro macro
ExtractHandlerContextInternal(implicit context: Context)(handler: Callable| ExtractHandlerContext(implicit context: Context)(handler: Callable|Undefined):
Undefined):
Context labels NotFound { Context labels NotFound {
let iter: JSAny = handler; let iter: JSAny = handler;
while (true) { while (true) {
...@@ -63,25 +62,16 @@ namespace promise { ...@@ -63,25 +62,16 @@ namespace promise {
goto NotFound; goto NotFound;
} }
macro // According to the HTML specification, we use the handler's context to
ExtractHandlerContext(implicit context: Context)(handler: Callable| // EnqueueJob for Promise resolution.
Undefined): Context {
try {
return ExtractHandlerContextInternal(handler) otherwise NotFound;
}
label NotFound deferred {
return context;
}
}
macro macro
ExtractHandlerContext(implicit context: Context)( ExtractHandlerContext(implicit context: Context)(
primary: Callable|Undefined, secondary: Callable|Undefined): Context { primary: Callable|Undefined, secondary: Callable|Undefined): Context {
try { try {
return ExtractHandlerContextInternal(primary) otherwise NotFound; return ExtractHandlerContext(primary) otherwise NotFound;
} }
label NotFound deferred { label NotFound deferred {
return ExtractHandlerContextInternal(secondary) otherwise Default; return ExtractHandlerContext(secondary) otherwise Default;
} }
label Default deferred { label Default deferred {
return context; return context;
...@@ -102,9 +92,6 @@ namespace promise { ...@@ -102,9 +92,6 @@ namespace promise {
secondaryHandler = promiseReaction.fulfill_handler; secondaryHandler = promiseReaction.fulfill_handler;
} }
// According to HTML, we use the context of the appropriate handler as the
// context of the microtask. See step 3 of HTML's EnqueueJob:
// https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
const handlerContext: Context = const handlerContext: Context =
ExtractHandlerContext(primaryHandler, secondaryHandler); ExtractHandlerContext(primaryHandler, secondaryHandler);
......
...@@ -130,10 +130,10 @@ namespace promise { ...@@ -130,10 +130,10 @@ namespace promise {
macro NewPromiseResolveThenableJobTask(implicit context: Context)( macro NewPromiseResolveThenableJobTask(implicit context: Context)(
promiseToResolve: JSPromise, then: JSReceiver, thenable: JSReceiver, promiseToResolve: JSPromise, then: JSReceiver, thenable: JSReceiver,
thenContext: Context): PromiseResolveThenableJobTask { thenableContext: Context): PromiseResolveThenableJobTask {
return new PromiseResolveThenableJobTask{ return new PromiseResolveThenableJobTask{
map: PromiseResolveThenableJobTaskMapConstant(), map: PromiseResolveThenableJobTaskMapConstant(),
context: thenContext, context: thenableContext,
promise_to_resolve: promiseToResolve, promise_to_resolve: promiseToResolve,
then: then, then: then,
thenable: thenable thenable: thenable
......
...@@ -177,14 +177,7 @@ namespace promise { ...@@ -177,14 +177,7 @@ namespace promise {
label Enqueue { label Enqueue {
// 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob,
// «promise, resolution, thenAction»). // «promise, resolution, thenAction»).
const nativeContext = LoadNativeContext(context);
// According to HTML, we use the context of the then function
// (|thenAction|) as the context of the microtask. See step 3 of HTML's
// EnqueueJob:
// https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
const thenContext: Context =
ExtractHandlerContext(UnsafeCast<Callable>(then));
const nativeContext = LoadNativeContext(thenContext);
const task = NewPromiseResolveThenableJobTask( const task = NewPromiseResolveThenableJobTask(
promise, UnsafeCast<JSReceiver>(then), promise, UnsafeCast<JSReceiver>(then),
UnsafeCast<JSReceiver>(resolution), nativeContext); UnsafeCast<JSReceiver>(resolution), nativeContext);
......
...@@ -6017,20 +6017,10 @@ MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise, ...@@ -6017,20 +6017,10 @@ MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise,
// 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob,
// «promise, resolution, thenAction»). // «promise, resolution, thenAction»).
// According to HTML, we use the context of the then function (|thenAction|)
// as the context of the microtask. See step 3 of HTML's EnqueueJob:
// https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
Handle<NativeContext> then_context;
if (!JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(then_action))
.ToHandle(&then_context)) {
then_context = isolate->native_context();
}
Handle<PromiseResolveThenableJobTask> task = Handle<PromiseResolveThenableJobTask> task =
isolate->factory()->NewPromiseResolveThenableJobTask( isolate->factory()->NewPromiseResolveThenableJobTask(
promise, Handle<JSReceiver>::cast(then_action), promise, Handle<JSReceiver>::cast(then_action),
Handle<JSReceiver>::cast(resolution), then_context); Handle<JSReceiver>::cast(resolution), isolate->native_context());
if (isolate->debug()->is_active() && resolution->IsJSPromise()) { if (isolate->debug()->is_active() && resolution->IsJSPromise()) {
// Mark the dependency of the new {promise} on the {resolution}. // Mark the dependency of the new {promise} on the {resolution}.
Object::SetProperty(isolate, resolution, Object::SetProperty(isolate, resolution,
...@@ -6038,7 +6028,8 @@ MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise, ...@@ -6038,7 +6028,8 @@ MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise,
promise) promise)
.Check(); .Check();
} }
MicrotaskQueue* microtask_queue = then_context->microtask_queue(); MicrotaskQueue* microtask_queue =
isolate->native_context()->microtask_queue();
if (microtask_queue) microtask_queue->EnqueueMicrotask(*task); if (microtask_queue) microtask_queue->EnqueueMicrotask(*task);
// 13. Return undefined. // 13. Return undefined.
...@@ -6074,9 +6065,6 @@ Handle<Object> JSPromise::TriggerPromiseReactions(Isolate* isolate, ...@@ -6074,9 +6065,6 @@ Handle<Object> JSPromise::TriggerPromiseReactions(Isolate* isolate,
Handle<PromiseReaction> reaction = Handle<PromiseReaction>::cast(task); Handle<PromiseReaction> reaction = Handle<PromiseReaction>::cast(task);
reactions = handle(reaction->next(), isolate); reactions = handle(reaction->next(), isolate);
// According to HTML, we use the context of the appropriate handler as the
// context of the microtask. See step 3 of HTML's EnqueueJob:
// https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
Handle<NativeContext> handler_context; Handle<NativeContext> handler_context;
Handle<HeapObject> primary_handler; Handle<HeapObject> primary_handler;
......
...@@ -68,15 +68,7 @@ using TestWithNativeContextAndFinalizationRegistry = // ...@@ -68,15 +68,7 @@ using TestWithNativeContextAndFinalizationRegistry = //
WithSharedIsolateMixin< // WithSharedIsolateMixin< //
::testing::Test>>>>>; ::testing::Test>>>>>;
namespace { class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationRegistry {
void DummyPromiseHook(PromiseHookType type, Local<Promise> promise,
Local<Value> parent) {}
} // namespace
class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationRegistry,
public ::testing::WithParamInterface<bool> {
public: public:
template <typename F> template <typename F>
Handle<Microtask> NewMicrotask(F&& f) { Handle<Microtask> NewMicrotask(F&& f) {
...@@ -90,12 +82,6 @@ class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationRegistry, ...@@ -90,12 +82,6 @@ class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationRegistry,
void SetUp() override { void SetUp() override {
microtask_queue_ = MicrotaskQueue::New(isolate()); microtask_queue_ = MicrotaskQueue::New(isolate());
native_context()->set_microtask_queue(microtask_queue()); native_context()->set_microtask_queue(microtask_queue());
if (GetParam()) {
// Use a PromiseHook to switch the implementation to ResolvePromise
// runtime, instead of ResolvePromise builtin.
v8_isolate()->SetPromiseHook(&DummyPromiseHook);
}
} }
void TearDown() override { void TearDown() override {
...@@ -140,7 +126,7 @@ class RecordingVisitor : public RootVisitor { ...@@ -140,7 +126,7 @@ class RecordingVisitor : public RootVisitor {
}; };
// Sanity check. Ensure a microtask is stored in a queue and run. // Sanity check. Ensure a microtask is stored in a queue and run.
TEST_P(MicrotaskQueueTest, EnqueueAndRun) { TEST_F(MicrotaskQueueTest, EnqueueAndRun) {
bool ran = false; bool ran = false;
EXPECT_EQ(0, microtask_queue()->capacity()); EXPECT_EQ(0, microtask_queue()->capacity());
EXPECT_EQ(0, microtask_queue()->size()); EXPECT_EQ(0, microtask_queue()->size());
...@@ -156,7 +142,7 @@ TEST_P(MicrotaskQueueTest, EnqueueAndRun) { ...@@ -156,7 +142,7 @@ TEST_P(MicrotaskQueueTest, EnqueueAndRun) {
} }
// Check for a buffer growth. // Check for a buffer growth.
TEST_P(MicrotaskQueueTest, BufferGrowth) { TEST_F(MicrotaskQueueTest, BufferGrowth) {
int count = 0; int count = 0;
// Enqueue and flush the queue first to have non-zero |start_|. // Enqueue and flush the queue first to have non-zero |start_|.
...@@ -190,7 +176,7 @@ TEST_P(MicrotaskQueueTest, BufferGrowth) { ...@@ -190,7 +176,7 @@ TEST_P(MicrotaskQueueTest, BufferGrowth) {
} }
// MicrotaskQueue instances form a doubly linked list. // MicrotaskQueue instances form a doubly linked list.
TEST_P(MicrotaskQueueTest, InstanceChain) { TEST_F(MicrotaskQueueTest, InstanceChain) {
ClearTestMicrotaskQueue(); ClearTestMicrotaskQueue();
MicrotaskQueue* default_mtq = isolate()->default_microtask_queue(); MicrotaskQueue* default_mtq = isolate()->default_microtask_queue();
...@@ -221,7 +207,7 @@ TEST_P(MicrotaskQueueTest, InstanceChain) { ...@@ -221,7 +207,7 @@ TEST_P(MicrotaskQueueTest, InstanceChain) {
// Pending Microtasks in MicrotaskQueues are strong roots. Ensure they are // Pending Microtasks in MicrotaskQueues are strong roots. Ensure they are
// visited exactly once. // visited exactly once.
TEST_P(MicrotaskQueueTest, VisitRoot) { TEST_F(MicrotaskQueueTest, VisitRoot) {
// Ensure that the ring buffer has separate in-use region. // Ensure that the ring buffer has separate in-use region.
for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) { for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
microtask_queue()->EnqueueMicrotask(*NewMicrotask([] {})); microtask_queue()->EnqueueMicrotask(*NewMicrotask([] {}));
...@@ -247,7 +233,7 @@ TEST_P(MicrotaskQueueTest, VisitRoot) { ...@@ -247,7 +233,7 @@ TEST_P(MicrotaskQueueTest, VisitRoot) {
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
} }
TEST_P(MicrotaskQueueTest, PromiseHandlerContext) { TEST_F(MicrotaskQueueTest, PromiseHandlerContext) {
Local<v8::Context> v8_context2 = v8::Context::New(v8_isolate()); Local<v8::Context> v8_context2 = v8::Context::New(v8_isolate());
Local<v8::Context> v8_context3 = v8::Context::New(v8_isolate()); Local<v8::Context> v8_context3 = v8::Context::New(v8_isolate());
Local<v8::Context> v8_context4 = v8::Context::New(v8_isolate()); Local<v8::Context> v8_context4 = v8::Context::New(v8_isolate());
...@@ -341,7 +327,7 @@ TEST_P(MicrotaskQueueTest, PromiseHandlerContext) { ...@@ -341,7 +327,7 @@ TEST_P(MicrotaskQueueTest, PromiseHandlerContext) {
v8_context2->DetachGlobal(); v8_context2->DetachGlobal();
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_Enqueue) { TEST_F(MicrotaskQueueTest, DetachGlobal_Enqueue) {
EXPECT_EQ(0, microtask_queue()->size()); EXPECT_EQ(0, microtask_queue()->size());
// Detach MicrotaskQueue from the current context. // Detach MicrotaskQueue from the current context.
...@@ -353,7 +339,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Enqueue) { ...@@ -353,7 +339,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Enqueue) {
EXPECT_EQ(0, microtask_queue()->size()); EXPECT_EQ(0, microtask_queue()->size());
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_Run) { TEST_F(MicrotaskQueueTest, DetachGlobal_Run) {
EXPECT_EQ(0, microtask_queue()->size()); EXPECT_EQ(0, microtask_queue()->size());
// Enqueue microtasks to the current context. // Enqueue microtasks to the current context.
...@@ -391,7 +377,18 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Run) { ...@@ -391,7 +377,18 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Run) {
} }
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) { namespace {
void DummyPromiseHook(PromiseHookType type, Local<Promise> promise,
Local<Value> parent) {}
} // namespace
TEST_F(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) {
// Use a PromiseHook to switch the implementation to ResolvePromise runtime,
// instead of ResolvePromise builtin.
v8_isolate()->SetPromiseHook(&DummyPromiseHook);
RunJS( RunJS(
"var resolve;" "var resolve;"
"var promise = new Promise(r => { resolve = r; });" "var promise = new Promise(r => { resolve = r; });"
...@@ -413,50 +410,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) { ...@@ -413,50 +410,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) {
EXPECT_EQ(0, microtask_queue()->size()); EXPECT_EQ(0, microtask_queue()->size());
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_ResolveThenableForeignThen) { TEST_F(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
Handle<JSArray> result = RunJS<JSArray>(
"let result = [false];"
"result");
Handle<JSFunction> then = RunJS<JSFunction>("() => { result[0] = true; }");
Handle<JSPromise> stale_promise;
Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
std::unique_ptr<MicrotaskQueue> sub_microtask_queue =
MicrotaskQueue::New(isolate());
Utils::OpenHandle(*sub_context)
->native_context()
.set_microtask_queue(microtask_queue());
{
v8::Context::Scope scope(sub_context);
CHECK(sub_context->Global()
->Set(sub_context, NewString("then"),
Utils::ToLocal(Handle<JSReceiver>::cast(then)))
.FromJust());
EXPECT_EQ(0, microtask_queue()->size());
EXPECT_EQ(0, sub_microtask_queue->size());
EXPECT_TRUE(
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsFalse());
stale_promise = RunJS<JSPromise>("Promise.resolve({ then })");
EXPECT_EQ(1, microtask_queue()->size());
EXPECT_EQ(0, sub_microtask_queue->size());
EXPECT_TRUE(
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsFalse());
}
sub_context->DetachGlobal();
sub_context.Clear();
sub_microtask_queue.reset();
EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
EXPECT_EQ(0, microtask_queue()->size());
EXPECT_TRUE(
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsTrue());
}
TEST_P(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
// EnqueueMicrotask should use the context associated to the handler instead // EnqueueMicrotask should use the context associated to the handler instead
// of the current context. E.g. // of the current context. E.g.
// // At Context A. // // At Context A.
...@@ -535,7 +489,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_HandlerContext) { ...@@ -535,7 +489,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
.FromJust()); .FromJust());
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_Chain) { TEST_F(MicrotaskQueueTest, DetachGlobal_Chain) {
Handle<JSPromise> stale_rejected_promise; Handle<JSPromise> stale_rejected_promise;
Local<v8::Context> sub_context = v8::Context::New(v8_isolate()); Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
...@@ -562,7 +516,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Chain) { ...@@ -562,7 +516,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_Chain) {
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsTrue()); Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsTrue());
} }
TEST_P(MicrotaskQueueTest, DetachGlobal_InactiveHandler) { TEST_F(MicrotaskQueueTest, DetachGlobal_InactiveHandler) {
Local<v8::Context> sub_context = v8::Context::New(v8_isolate()); Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
Utils::OpenHandle(*sub_context) Utils::OpenHandle(*sub_context)
->native_context() ->native_context()
...@@ -604,7 +558,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_InactiveHandler) { ...@@ -604,7 +558,7 @@ TEST_P(MicrotaskQueueTest, DetachGlobal_InactiveHandler) {
Object::GetElement(isolate(), result, 1).ToHandleChecked()->IsFalse()); Object::GetElement(isolate(), result, 1).ToHandleChecked()->IsFalse());
} }
TEST_P(MicrotaskQueueTest, MicrotasksScope) { TEST_F(MicrotaskQueueTest, MicrotasksScope) {
ASSERT_NE(isolate()->default_microtask_queue(), microtask_queue()); ASSERT_NE(isolate()->default_microtask_queue(), microtask_queue());
microtask_queue()->set_microtasks_policy(MicrotasksPolicy::kScoped); microtask_queue()->set_microtasks_policy(MicrotasksPolicy::kScoped);
...@@ -620,11 +574,5 @@ TEST_P(MicrotaskQueueTest, MicrotasksScope) { ...@@ -620,11 +574,5 @@ TEST_P(MicrotaskQueueTest, MicrotasksScope) {
EXPECT_TRUE(ran); EXPECT_TRUE(ran);
} }
INSTANTIATE_TEST_SUITE_P(
, MicrotaskQueueTest, ::testing::Values(false, true),
[](const ::testing::TestParamInfo<MicrotaskQueueTest::ParamType>& info) {
return info.param ? "runtime" : "builtin";
});
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment