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 Houdini worklets #16814
Implement Houdini worklets #16814
Conversation
Heads up! This PR modifies the following files:
|
r? @jdm |
cc @bfgeek |
Some things that should probably be fixed before this lands:
The code is ready for review as-is though. |
Hi @bfgeek! This is implementation of worklets in Servo, using a thread pool to ensure worklet execution isn't blocked by GC or by module loading. Mostly the spec was pretty straightforward to implement, you'll be pleased to hear! |
The spec issues raised by implementing worklets (and starting on paint worklets):
|
cc/ @nhiroki who works on our implementation. That's great to see! Thanks for filing spec issues. |
☔ The latest upstream changes (presumably #16845) made this pull request unmergeable. Please resolve the merge conflicts. |
// https://drafts.css-houdini.org/worklets/#examples | ||
partial interface Window { | ||
[Pref="dom.worklet.testing.enabled", SameObject] readonly attribute Worklet testWorklet; | ||
[Pref="dom.worklet.testing.enabled"] DOMString? testWorkletLookup(DOMString key); |
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 made a separate DOM interface with a constructor so we don't need to include test-only fields in Window?
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.
Will do.
pub mem_profiler_chan: mem::ProfilerChan, | ||
/// Chan to the time profiler | ||
pub time_profiler_chan: time::ProfilerChan, | ||
/// Chan to devtools |
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 use channel instead of chan in these comments.
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.
Will do.
use style::thread_state; | ||
use swapper::Swapper; | ||
use swapper::swapper; | ||
use uuid::Uuid; |
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.
Any reason not to collapse some of these import statements from the same modules?
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.
Minimizing the chance of annoying merge conflicts.
components/script/dom/worklet.rs
Outdated
let global = window.upcast::<GlobalScope>(); | ||
unsafe { | ||
WorkletBinding::Wrap(global.get_cx(), global, worklet) | ||
} |
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 should use reflect_dom_object
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.
I don't think you can use reflect_dom_object
to create globals can you?
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.
WorkletBinding isn't a 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.
Oh good point, we're in Worklet
not WorkletGlobalScope
.
self.worklet_id | ||
} | ||
|
||
#[allow(dead_code)] |
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.
This is dead code, just in for future-proofing. I can get rid of it.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, JSTraceable)] | ||
pub struct WorkletId(Uuid); | ||
|
||
known_heap_size!(0, WorkletId); |
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 prefer using #[ignore_heap_size_of = "Can't measure uuid"]
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.
Will do.
} | ||
|
||
fn decrement_counter_by(&self, offset: isize) -> isize { | ||
self.0.fetch_sub(offset, Ordering::AcqRel) |
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.
Why AcqRel instead of SeqCst?
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.
Using the weakest ordering that does the job. In this case it doesn't matter a huge amount, this isn't hot code.
debug!("Finished adding script."); | ||
let old_counter = pending_tasks_struct.decrement_counter_by(1); | ||
if old_counter == 1 { | ||
// TODO: trigger a reflow? |
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 happens automatically when events run in the script thread. Did you mean something more specific?
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.
At this point, we have loaded a worklet, but not actually run it (e.g. to get a paint worklet to do its painting).
components/script/dom/worklet.rs
Outdated
} | ||
|
||
/// Fetch and invoke a worklet script. | ||
///g https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script |
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: g
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.
Oops.
As usual, I found these changes to be a pleasant and straightforward read. Good job! |
components/script/dom/worklet.rs
Outdated
type_: RequestType::Script, | ||
destination: Destination::Script, | ||
credentials_mode: credentials.into(), | ||
.. RequestInit::default() |
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 should be using CORS mode here, per https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script. We'll need to include a real origin, too.
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.
CORS mode isn't a problem, but I'm not sure which origin to use here. One snag is that this is being done in a different thread, so we don't have access to the MutableOrigin
of the window.
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.
Send an ImmutableOrigin from the window as part of the initiation?
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.
Thanks for the review!
// https://drafts.css-houdini.org/worklets/#examples | ||
partial interface Window { | ||
[Pref="dom.worklet.testing.enabled", SameObject] readonly attribute Worklet testWorklet; | ||
[Pref="dom.worklet.testing.enabled"] DOMString? testWorkletLookup(DOMString key); |
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.
Will do.
pub mem_profiler_chan: mem::ProfilerChan, | ||
/// Chan to the time profiler | ||
pub time_profiler_chan: time::ProfilerChan, | ||
/// Chan to devtools |
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.
Will do.
use style::thread_state; | ||
use swapper::Swapper; | ||
use swapper::swapper; | ||
use uuid::Uuid; |
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.
Minimizing the chance of annoying merge conflicts.
components/script/dom/worklet.rs
Outdated
let global = window.upcast::<GlobalScope>(); | ||
unsafe { | ||
WorkletBinding::Wrap(global.get_cx(), global, worklet) | ||
} |
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 don't think you can use reflect_dom_object
to create globals can you?
self.worklet_id | ||
} | ||
|
||
#[allow(dead_code)] |
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 is dead code, just in for future-proofing. I can get rid of it.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, JSTraceable)] | ||
pub struct WorkletId(Uuid); | ||
|
||
known_heap_size!(0, WorkletId); |
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.
Will do.
} | ||
|
||
fn decrement_counter_by(&self, offset: isize) -> isize { | ||
self.0.fetch_sub(offset, Ordering::AcqRel) |
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.
Using the weakest ordering that does the job. In this case it doesn't matter a huge amount, this isn't hot code.
components/script/dom/worklet.rs
Outdated
} | ||
|
||
/// Fetch and invoke a worklet script. | ||
///g https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script |
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.
Oops.
components/script/dom/worklet.rs
Outdated
type_: RequestType::Script, | ||
destination: Destination::Script, | ||
credentials_mode: credentials.into(), | ||
.. RequestInit::default() |
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.
CORS mode isn't a problem, but I'm not sure which origin to use here. One snag is that this is being done in a different thread, so we don't have access to the MutableOrigin
of the window.
debug!("Finished adding script."); | ||
let old_counter = pending_tasks_struct.decrement_counter_by(1); | ||
if old_counter == 1 { | ||
// TODO: trigger a reflow? |
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.
At this point, we have loaded a worklet, but not actually run it (e.g. to get a paint worklet to do its painting).
10a9a04
to
6a4b4e6
Compare
Squash and r=me. |
67eed3e
to
de96995
Compare
@bors-servo r=jdm |
📌 Commit de96995 has been approved by |
Implement Houdini worklets <!-- Please describe your changes on the following line: --> This PR implements the current draft Houdini Worklets specification (https://drafts.css-houdini.org/worklets/). The implementation is intended to provide a responsive environment for worklet execution, and in particular to ensure that the primary worklet executor does not garbage collect, and does not block loading module code. The implementation does this by providing a thread pool, and performing GC and module loading in a backup thread, not in the primary thread. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #16206 - [x] There are tests for these changes <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- 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/16814) <!-- Reviewable:end -->
💔 Test failed - linux-rel-wpt |
We can either hide the Worklet interface behind an off-by-default pref or update the test to include the interface in the expected list. |
de96995
to
af8436c
Compare
I added |
📌 Commit af8436c has been approved by |
Implement Houdini worklets <!-- Please describe your changes on the following line: --> This PR implements the current draft Houdini Worklets specification (https://drafts.css-houdini.org/worklets/). The implementation is intended to provide a responsive environment for worklet execution, and in particular to ensure that the primary worklet executor does not garbage collect, and does not block loading module code. The implementation does this by providing a thread pool, and performing GC and module loading in a backup thread, not in the primary thread. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #16206 - [x] There are tests for these changes <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- 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/16814) <!-- Reviewable:end -->
☀️ Test successful - android, arm32, arm64, linux-dev, linux-rel-css, linux-rel-wpt, mac-dev-unit, mac-rel-css, mac-rel-wpt1, mac-rel-wpt2, windows-msvc-dev |
This PR implements the current draft Houdini Worklets specification (https://drafts.css-houdini.org/worklets/).
The implementation is intended to provide a responsive environment for worklet execution, and in particular to ensure that the primary worklet executor does not garbage collect, and does not block loading module code. The implementation does this by providing a thread pool, and performing GC and module loading in a backup thread, not in the primary thread.
./mach build -d
does not report any errors./mach test-tidy
does not report any errorsThis change is