-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement unhandledrejection event #20755
Conversation
Heads up! This PR modifies the following files:
|
EventCancelable::Cancelable, | ||
// FIXME(CYBAI): Use real rejected `promise` and `reason` | ||
Promise::new(&global.upcast::<GlobalScope>()), | ||
HandleValue::null() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use Promise::new_with_js_promise
here if we make the method public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, new_with_js_promise
is what I should use and just make it as a pub
lic method.
Previously, I also tried this method but the assert
inside new_with_js_promise
complained about that the passed HandleObject
is not Promise
. But now it works fine! I think I used wrong HandleObject
before 😓
let result = GetPromiseResult(promise); | ||
rooted!(in(cx) let object = result.to_object()); | ||
let error_report = JS_ErrorFromException(cx, object.handle()); | ||
println!("{:?}, {:?}", result, error_report); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect this to look like:
rooted!(in(cx) let result = GetPromiseResult(promise));
We can then use result.handle()
to object a HandleValue
. I don't think there's any reason to expect an exception object here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdm Yeah! With using rooted
macro, it works that I can cast the type of result
to be HandleValue
! Thanks a lot!
About the exception object, we don't need it for unhandlerejection
so just removed it!
EventCancelable::Cancelable, | ||
// FIXME(CYBAI): Use real rejected `promise` and `reason` | ||
Promise::new(&global.upcast::<GlobalScope>()), | ||
HandleValue::null() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use Promise::new_with_js_promise
here if we make the method public.
583beaa
to
3071ff5
Compare
global.set_uncaught_rejections(promise); | ||
|
||
unsafe { | ||
PromiseDebugging::flush_uncaught_rejections_internal(global); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Gecko, flush uncaught rejections internal
will only be triggered in FlushSync
.
So, I'd like to confirm if I'm correct about doing the similar implementation in Servo like following:
- Make the received uncaught rejection be queued into
script thread
withtask
which should be the step 4 in notify-about-rejected-promises spec instead of running theflush_uncaught_rejections_internal
directly whenAddUncaughtRejection
executed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdm ping for asking question, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to use task
macro to handle Queue a task
for unhandled rejection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not responding. Yes, queuing a task using the task macro is the right way to implement step 4. However, that task should only be queued once per run of the event loop, and I believe AddUncaughtRejection can run an arbitrary number of times per run of the event loop. You'll want to invoke the notify about rejected promises algorithm as part of the perform a microtask checkpoint algorithm.
3071ff5
to
aa0254d
Compare
} | ||
} | ||
|
||
pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this method and add the initial values as arguments to new_inherited.
pub struct PromiseRejectionEvent { | ||
event: Event, | ||
#[ignore_malloc_size_of = "Rc"] | ||
promise: DomRefCell<Rc<Promise>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't need the DomRefCell if we pass the initial promise value to new_inherited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to not use the DomRefCell
here!
}; | ||
|
||
dictionary PromiseRejectionEventInit : EventInit { | ||
Promise<any> promise; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add /* required */
here, since it's in the spec but we don't support it yet.
components/script/dom/globalscope.rs
Outdated
@@ -202,6 +222,22 @@ impl GlobalScope { | |||
GlobalScope::from_object(obj) | |||
} | |||
|
|||
pub fn set_uncaught_rejections(&self, rejection: HandleObject) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this add_uncaught_rejection
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, using add
as prefix for this function makes more sense! Thanks!
components/script/dom/globalscope.rs
Outdated
&self.uncaught_rejections | ||
} | ||
|
||
pub fn set_consumed_rejections(&self, rejection: HandleObject) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this add_consumed_rejection
.
} | ||
|
||
#[allow(unsafe_code)] | ||
pub unsafe fn flush_uncaught_rejections_internal(global: DomRoot<GlobalScope>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this function into script_runtime.rs; there's no need for the promisedebugging.rs file to exist. We should add a comment pointing at https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises and call this function notify_about_rejected_promises
. We will need to call it from MicrotaskQueue::checkpoint
in the TODO in step 8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this comment, I still doesn't implement it yet due to below question:
I tried to figure out how to call notify_about_rejected_promises
inside MicrotaskQueue::checkpoint
.
Due to GlobalScope
as an argument in notify_about_rejected_promises
, we'll need to pass a GlobalScope
from MicrotaskQueue::checkpoint
but it seems that we don't have a way to get GlobalScope
in MicrotaskQueue
. So, maybe we can only pass a GlobalScope
to it.
Ex.
pub fn checkpoint<F>(&self, global: GlobalScope, target_provider: F)
where F: Fn(PipelineId) -> Option<DomRoot<GlobalScope>>
{
However, it will have an issue that we'll call MicrotaskQueue::checkpoint
in two places:
One is in ScriptThread
:
servo/components/script/script_thread.rs
Lines 2653 to 2655 in d23bc4f
fn perform_a_microtask_checkpoint(&self) { | |
self.microtask_queue.checkpoint(|id| self.documents.borrow().find_global(id)) | |
} |
and the other is in GlobalScope
:
servo/components/script/dom/globalscope.rs
Lines 521 to 524 in d23bc4f
/// Perform a microtask checkpoint. | |
pub fn perform_a_microtask_checkpoint(&self) { | |
self.microtask_queue.checkpoint(|_| Some(DomRoot::from_ref(self))); | |
} |
We must be able to get the globalscope
inside GlobalScope
module but we don't have globalscope
under every ScriptThread
.
How can we have GlobalScope
for both caller places and pass it to notify_about_rejected_promises
?
Do you have any advises?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While investigating this question, I discovered and filed #20908. Since step 3 says we need to notify about rejected promises for each global associated with the script thread, I propose that the checkpoint
method should accept a Vec<DomRoot<GlobalScope>>
argument and iterate over it to notify all of the contained globals. For ScriptThread, this can be created by iterating over all known documents and getting their global. For GlobalScope, it can be a vector consisting of the self object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing an vector of GlobalScope
sounds reasonable to me and I will try it.
But, my only concern to the solution is that, in script thread, if there's no any global in it, then it won't trigger notify_about_rejected_promises
method from checkpoint
method, is this reasonable?
Or, that said, after having a solution for it like #20908 mentioned; then, we will have corresponding global in each checkpoint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of a way to have a script thread with no globals while still having rejected promises that require notification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. While having rejected promises, it should have its own globals.
I will try to use the vector way to implement it, thanks!
components/script/script_runtime.rs
Outdated
match state { | ||
PromiseRejectionHandlingState::Unhandled => { | ||
let global = GlobalScope::from_context(cx); | ||
PromiseDebugging::AddUncaughtRejection(global, promise); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can call global.add_uncaught_rejection
directly instead of using PromiseDebugging.
pub unsafe fn flush_uncaught_rejections_internal(global: DomRoot<GlobalScope>) { | ||
let cx = global.get_cx(); | ||
|
||
global.get_uncaught_rejections().borrow().iter().for_each(|promise: &Box<Heap<*mut JSObject>>| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the task from step 4, we need to create a RootedVec to hold the list of promises before we clear the original list. We can then use that vector in the task, rather than global.get_uncaught_rejections()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to address this comment when I call notify_about_rejected_promises
inside MicrotaskQueue::checkpoint
successfully.
aa0254d
to
0c9042a
Compare
5a46ecf
to
eb7d954
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really close!
reason: HandleValue | ||
) -> DomRoot<Self> { | ||
let ev = reflect_dom_object( | ||
Box::new(PromiseRejectionEvent::new_inherited(promise.clone())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no need for the clone.
components/script/script_runtime.rs
Outdated
|
||
#[allow(unsafe_code, unrooted_must_root)] | ||
/// https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises | ||
pub unsafe fn notify_about_rejected_promises(global: DomRoot<GlobalScope>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's take &GlobalScope
as an argument instead. And instead of making this function unsafe, we should use unsafe blocks in the implementation.
components/script/script_runtime.rs
Outdated
atom!("unhandledrejection"), | ||
EventBubbles::DoesNotBubble, | ||
EventCancelable::Cancelable, | ||
promise.duplicate(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just pass promise
instead?
components/script/script_thread.rs
Outdated
|id| self.documents.borrow().find_global(id), | ||
self.documents.borrow() | ||
.iter() | ||
.filter_map(|(id, _doc)| self.documents.borrow().find_global(id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we use map and return document.global()
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! Sounds good idea! I will try it! Thanks!
Implement unhandledrejection event --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #15412 - [x] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/20755) <!-- Reviewable:end -->
☀️ Test successful - android, android-mac, android-x86, arm32, arm64, linux-dev, linux-rel-css, linux-rel-wpt, mac-dev-unit, mac-rel-css1, mac-rel-css2, mac-rel-wpt1, mac-rel-wpt2, mac-rel-wpt3, mac-rel-wpt4, status-taskcluster, windows-msvc-dev |
@bors-servo r+ |
📌 Commit 24f1afd has been approved by |
@bors-servo ping |
😪 I'm awake I'm awake |
Implement unhandledrejection event --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #15412 - [x] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/20755) <!-- Reviewable:end -->
This is still shown as "pending" in Homu’s queue although Buildbot doesn’t show any running jobs. Let’s try to get things unstuck. |
Opened new PR for upstreamable changes. Completed upstream sync of web-platform-test changes at web-platform-tests/wpt#13681. |
Hmm.. looks like |
@bors-servo retry |
Implement unhandledrejection event --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #15412 - [x] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/20755) <!-- Reviewable:end -->
☀️ Test successful - android, android-mac, android-x86, arm32, arm64, linux-dev, linux-rel-css, linux-rel-wpt, mac-dev-unit, mac-rel-css1, mac-rel-css2, mac-rel-wpt1, mac-rel-wpt2, mac-rel-wpt3, mac-rel-wpt4, status-taskcluster, windows-msvc-dev |
./mach build -d
does not report any errors./mach test-tidy
does not report any errorsThis change is