issue: servo/servo#1799, https://github.com/servo/servo/wiki/Prototype-ways-of-splitting-the-script-crate
blog: https://servo.org/blog/2018/08/09/gsoc-generic-servo/
jdm's minimal example (already contains codegen changes): servo/servo#1799 (comment)
jdm's idea with asajeffrey's idea:
mod script_bindings {
// this module can be considered as "forward declaratins"
// but in servo it should also contain all generated stuff from codegen (uses)
// while actual defieinitions/implementations is outside
pub trait TypeHolderTrait: Sized {
type Dom: DomTrait<Self>;
type WGPU: WgpuTrait<Self>;
}
// all exposed functions need to be behind trait
// this is not a problem for webidl exposed methods
// as the trait for them is generated by codegen
pub trait DomTrait<TH: TypeHolderTrait> {
fn method_on_dom(&self) -> &TH::WGPU;
}
pub trait WgpuTrait<TH: TypeHolderTrait> {
fn method_on_wgpu(&self) -> &TH::Dom;
}
}
mod script {
use crate::script_bindings::TypeHolderTrait;
use crate::script_bindings::DomTrait;
pub struct ActualDom<TH: TypeHolderTrait> {
wgpu: TH::WGPU,
}
/// This method is not exposed via traits,
/// so users needs to depend on script directly
///
/// See `uses_no_trait` for usage
impl<TH: TypeHolderTrait> ActualDom<TH> {
pub fn no_trait(&self) {
}
}
impl<TH: TypeHolderTrait> DomTrait<TH> for ActualDom<TH> {
fn method_on_dom(&self) -> &TH::WGPU {
&self.wgpu
}
}
pub fn start<TH: TypeHolderTrait>() {
// ...
}
}
// This should be done in stage 2 after bindings/impl separations is done
mod script_webgpu {
use crate::script_bindings::TypeHolderTrait;
use crate::script_bindings::WgpuTrait;
pub struct ActualWGPU<TH: TypeHolderTrait> {
dom: TH::Dom,
}
/// Here we use associated type binding syntax to tell compiler
/// to use script::ActualDom for Dom type
/// this allows us to call methods on ActualDom that were not "forward declered" via traits
impl<TH: TypeHolderTrait<Dom = crate::script::ActualDom<TH>>> ActualWGPU<TH> {
pub fn uses_no_trait(&self) {
self.dom.no_trait()
}
}
impl<TH: TypeHolderTrait> WgpuTrait<TH> for ActualWGPU<TH> {
fn method_on_wgpu(&self) -> &TH::Dom {
&self.dom
}
}
}
/// This links implementations from script-* with "forward declarations" from script_bindings
pub struct TypeHolder;
impl script_bindings::TypeHolderTrait for TypeHolder {
type Dom = script::ActualDom<Self>;
type WGPU = script_webgpu::ActualWGPU<Self>;
}
fn main() {
// when starting script we just need to put puzzle together
script::start::<TypeHolder>();
}
servo/servo#1799 (comment): For non-IDL methods that are impl on concrete DOM types we can use specific traits for for concrete DOM types (ex: GlobalScopeHelpers
that would be implemented on GlobalScope
), to add this to traits that are impl on DomTypes::GlobalScope
, we can use new Bindings.conf field:
'GlobalScope': {
'traits': ['GlobalScopeHelpers'],
},
and user will have to manually define it in core script crate:
pub trait GlobalScopeHelpers<D: crate::codegen::DomTypes::DomTypes<GlobalScope = Self>> {
fn global_scope_from_object(obj: *mut JSObject) -> DomRoot<Self>;
fn global_scope_origin(global: &Self) -> &servo_url::MutableOrigin;
}
but this is only worth doing on very commonly used methods, else we should just use associated type binding syntax (as showcased by uses_no_trait
).
functions that are not methods should simply be defined in core script with generic arg throw_dom_exception<D: DomTypes>(...)
.
components/
script/
lib.rs -- where we will link impl with declerations, home of TypeHolder struct
Cargo.toml
core/ -- core script stuff & bindings
lib.rs
Cargo.toml
...
main/ -- actual implementation for DOM
webgpu/ -- actual implementation for WebGPU DOM objects (stage 2)
... (place for future modules from stage 2)
- need global type holder everywhere (complexity; big because it contains each webidl type): https://github.com/servo/servo/pull/21371/files#diff-a460238adb3fd02364f082f64fe52a1a845192719b2629965488b19abd337358R210
- orphan rules can cause problems
- landing (tree lock during weekend)
- lost git blame
To properly evaluate result we need to check 3 things:
- build times (clean, incremental)
- binary size
- performance (specifically bindings performance, but general purpose benches should also do)
stage 1 only enabled seperation between codegen/impl stuff but we can make it more modular (see script_webgpu example), but all generated stuff will still live in single crate. This will make it easier to disable some script stuff via features.
The most interesting candidate for tryout is WebGPU, because is mostly standalone, only few points where it uses extrenal stuff:
- AsyncWGPUListener
Navigator includes NavigatorGPU
- gpucanvascontext
- some core stuff like promises and JS internals (external array), but those will be simply included from script-core
we need to reevaluate if this still brings us benefit! (although it might still be worth doing).
Parsers are also demonstrated to be standalone enough see servo/servo#21371 and by that point communty should be able to start helping us (peace by peace).
possible seperations:
- WebGL
- WebXR
- HTML
- ...
servo/servo#32883 (reply in thread)
will enable bigger isolation between modules (still needs to depend on depending modules)
all undefined are external where? type aliases for providers as we need to depend on them anyway
EX: canvas has multiple types of context
we would still want to use associated types in traits to inject types back to main (so this path is more like next step in even more modularization on top of previous work)