Repository: servo/servo (language: python)

Issue:
Split up the script crate for build speed
It's by far the largest crate in terms of binary size (2.5× as big as `main`).  Building it takes almost 40 seconds and 1.8 GB resident on my machine (with debugging enabled, optimization disabled).

The various DOM types and their generated bindings mostly don't depend on each other.  We could split them among a few crates to improve compile time (especially in parallel).

webgpu: Replace (Try)From (Try)Into with custom trait
As I already said in https://github.com/servo/servo/issues/33298#issuecomment-2331013056 we will need to roll our own trait for https://github.com/servo/servo/pull/33416 to work around orphan rules. One trait should be enough to handle all cases (Try variants will simply have T = Result<WGT, _>):

```rust
pub trait Into<T> {
    fn into_wgt(self) -> T;
}
```


Discussion / hints:
See also #925. I'm not actually convinced that they're as detached as everyone thinks; each DOM constructor calls a reflect method which takes a bindings-generated function argument, along with using generated types for things like unions/dictionaries, while all of the bindings code interacts with non-generated DOM types anytime they're used as arguments.

That is to say, I don't see a way to split it so that one crate depends on the other; there are all sorts of bidirectional dependencies that I can't see a clear way to break.

Splitting the script crate in a linear fashion may be more feasible. If we were able to move all of the generated bindings into a crate that script depends upon, that would mean that bindings and layout could build in parallel, and would reduce the working memory required to compile the rest of script. It would also speed up making changes to non-codegen parts of script.

I'd like to point out that this issue is more serious nowadays, as not only does the `script` crate totally dominate partial rebuild time (it takes several minutes for a non-optimised build now), but also the memory usage has gotten so large that it actually makes debug builds exceed the `3 GiB` virtual memory available on 32 bit systems, as reported in https://github.com/servo/servo/issues/13045 -- and it's probably just a matter of time until non-debug builds break as well...

I don't see how that would work. The generated bindings need to be able to inline the method definitions on the concrete type, and the concrete type depends fundamentally on the wrap function, and the class / JSNative definitions with it.

Latest idea from discussions with @asajeffrey:
* split generated bindings code into two crates - code that can be parameterized over FooTraits types (to avoid needing to import concrete DOM types), and code that needs to refer to the parameterized code using concrete types
* extract low-level types from script/ that the generated bindings code needs to refer to (e.g. converting native types to JS values) and put them in the generic bindings crate

This should give us the following crates:
js_bindings (generic codegen, generated types (eg. unions, dictioanries), common script/ types)
script (DOM API implementations and non-generic codegen)

This would mean that non-WebIDL/CodegenRust.py changes should not have to rebuild a significant portion of the generated bindings code.
Things that will make the above idea difficult:
* concrete DOM types in generated types (eg. unions or dictionaries containing WebIDL interface types)
At minimum, this will require collecting the list of WebIDL interfaces transitively used by a given WebIDL property (for each return value and argument), adding generic parameters for each, and propagating these generic types through layers like the union conversion methods when interacting with them.
Potential solution to the previous comment: a single trait with associated types for every WebIDL interface, and every generic method gets parameterized over the trait and uses T::Interface rather than Interface.
Or even better (?) make each FooMethods trait parametric, rather than each method inside FooMethods. Something like:

```
trait DOMTypes {
  type Foo: FooMethods<Self>;
  ...
}
trait FooMethods<DOM: DOMTypes> {
  fn bar(x: DOM::Bar) -> DOM::Baz;
  ...
}
struct DOM;
impl DOMTypes for DOM {
  type Foo = Foo;
  ...
}

struct Foo { ... }
impl FooMethods<DOM> for Foo { ... }
```
FooMethods would be unchanged. It's the standalone binding glue functions that need to be parametric.
How can FooMethods remain unchanged? Is the plan that FooMethods would be generated in the same crate as the concrete implementations? That does mean that the DOMTypes trait couldn't mention the FooMethods trait, but I don't know if that matters.
Oh right, FooMethods has arguments and return types too. Hooray!
I wonder if we can use `-> impl BarTypes` now.
We have quite a lot of places in the script crate where we assume the WebIDL method returns the concrete type, e.g. that `document.Window()` returns a `Root<Window>` not an `impl WindowMethods`.
My recommendation for how to go about this - to sort out fundamental problems quickly, I propose copying the codegen infrastructure into a new project, taking some representative WebIDL files and DOM implementations, and doing whatever it necessary to make it build unmodified. At that point the rebuild time should be _way_ smaller than Servo, which will yield much quicker feedback on experiments.
IRC conversation with @nox (initiated by @Wafflespeanut): http://logs.glob.uno/?c=mozilla%23servo&s=8+Mar+2017&e=8+Mar+2017#c626950

TL;DR:

a) splitting script is speculative, it might or might not work,
b) it might all be rendered moot by rustc improving incremental computation,
c) it would be a truck load of work, and
d) we love having this debate.
@asajeffrey b) is not entirely true: while incremental compilation *might* help with the major pain point of long build times after small changes, initial build time is also somewhat relevant; and even more importantly, it won't help with the memory usage problem.

(Maybe other compiler improvements could help with the latter, though?...)
True, I was mainly thinking about speeding up recompiling when editing DOM .rs and .webidl files. You can tell which case hurts me!
I don't think this is useful to keep this issue open. We know that splitting the script crate is something we can consider, but this will be done so far in the future (if ever) that I am hoping by then incremental compilation will make the crate size a non-issue.
@nox I want to point out, again, that incremental compilation will *not* make the memory footprint during build a non-issue.
The problem is still a real one, even if we don't have a clear vision of how to solve it, so I'd like to keep this issue open. If incremental computation makes this a non-issue, we can close the issue then.
>  Building it takes almost 40 seconds

Hahaha, the good old times
Any news on this issue ? Just having crashes by >4G memory consumption.
No news.
hmm, I'll try with kicking out stuff I dont wanna have anyways (bluetooth, gamepad, vr, ...).
unfortunately, #[cfg(...)] doesn't seem to work in many places - I really miss a real preprocessor.
Is the effort needed small, medium or xlarge?
The effort is still very large. I have proposed a [Google Summer of Code project](https://github.com/servo/servo/wiki/Prototype-ways-of-splitting-the-script-crate) to investigate the ideas we have discussed in this thread.
While trying to understand the idea, work that was already done, its problems and how associate types would be used for this I make this example:

```rust
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>() {
        // ...
    }
}

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>();
}
```

and I must say I am thrilled. I think it should be feasible to separate codegen bindings/actual implementations and later we could even split actual implementations (see `script_webgpu`)
I'm going to try hacking together a really minimal script_bindings that uses real codegen on one interface, just to try to tease out what changes are necessary.
Well we already have https://github.com/servo/servo/pull/21371, I was thinking to go big and separate all generated code with implementations (`TypeHolderTrait` would generated by codegen). The main question I have is how to land such change (due to size).
https://github.com/servo/servo/compare/main...jdm:servo:script-split is the branch I worked on while flying on planes today. It successfully compiles with `RUSTC_BOOTSTRAP=1`; why does https://github.com/servo/servo/blob/main/components/script/lib.rs#L5 work for the script crate but fails to compile in the new script_bindings crate?

This feels promising! The codegen in script_bindings is spitting out this monster trait for my test webidl collection:
```rust
pub trait DomTypes: crate::utils::DomHelpers<Self> + Sized {
    type CSSStyleDeclaration: crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::CSSStyleDeclaration>;
    type DOMException: crate::dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::DOMException>;
    type DOMParser: crate::dom::bindings::codegen::Bindings::DOMParserBinding::DOMParserMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::DOMParser>;
    type Document: crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::Document>;
    type GlobalScope: crate::dom::bindings::codegen::Bindings::GlobalScopeBinding::GlobalScopeMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::GlobalScope>;
    type Window: crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods<Self> + crate::reflector::DomObject + crate::reflector::DomGlobal<Self> + crate::conversions::IDLInterface + PartialEq + crate::reflector::DomObjectWrap<Self> + crate::inheritance::Castable + malloc_size_of::MallocSizeOf + crate::reflector::MutDomObject + crate::conversions::DerivedFrom<Self::GlobalScope> + crate::conversions::DerivedFrom<Self::Window>;
}
```
Then individual traits get parameterized like:
```rust
pub trait DOMParserMethods<D: DomTypes> {
    fn ParseFromString(&self, str: DOMString, type_: SupportedType) -> Fallible<DomRoot<D::Document>>;
    fn Constructor(global: &D::Window, proto: Option<HandleObject>) -> Result<DomRoot<D::DOMParser>, Error>;
}
```
And any bindings code that needs to interact with non-IDL methods for concrete DOM types needs to have the implementation provided by the script crate:
```rust
pub trait DomHelpers<D: crate::codegen::DomTypes::DomTypes> {
    fn throw_dom_exception(
        cx: crate::script_runtime::JSContext,
        global: &D::GlobalScope,
        result: crate::error::Error,
    );

    fn global_scope_from_object(obj: *mut JSObject) -> crate::root::DomRoot<D::GlobalScope>;

    fn global_scope_origin(global: &D::GlobalScope) -> &servo_url::MutableOrigin;
}
```
> It successfully compiles with RUSTC_BOOTSTRAP=1; why does https://github.com/servo/servo/blob/main/components/script/lib.rs#L5 work for the script crate but fails to compile in the new script_bindings crate?

Because we set `RUSTC_BOOTSTRAP` for specific crates here:
https://github.com/servo/servo/blob/2db9032e72bda6108b4eb5988eaf164141a61d64/.cargo/config.toml#L31-L33
> And any bindings code that needs to interact with non-IDL methods for concrete DOM types needs to have the implementation provided by the script crate

or use associated type binding syntax as shown above (will be needed for every consumer, so this is not appropriate for very used methods).
> > And any bindings code that needs to interact with non-IDL methods for concrete DOM types needs to have the implementation provided by the script crate
> 
> or use associated type binding syntax as shown above (will be needed for every consumer, so this is not appropriate for very used methods).

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:
```conf
'GlobalScope': {
    'traits': ['GlobalScopeHelpers'],
},
```
and user will have to manually define it in core script crate:
```rust
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;
}
```

functions that are not methods should simply be defined in core script with generic arg `throw_dom_exception<D: DomTypes>(...)`.
I have created a document with all stuff in one place (and some more ideas) on how to do this: https://gist.github.com/sagudev/c4a029cfccda9553adac32749a9ba583
I am working on getting the script_bindings crate in my branch compiling with all real webidl files present. It's going surprisingly well so far.
> I am working on getting the script_bindings crate in my branch compiling with all real webidl files present. It's going surprisingly well so far.

I was actually planing to do this, after I make sure that we can also measure runtime performance, hence my work on speedometer.
My script_bindings crate is now compiling with all DOM interfaces present \o/ Next step is getting the script crate to use tho generated bindings. https://github.com/jdm/servo/tree/script-split has all the latest changes.
I've made good progress in that branch, and I've found ways to make the changes in script/ minimally invasive, but I'm now running into errors that make no sense and feel like some kind of compiler bug. There are thousands of errors where the compiler can't resolve normal impl method calls, and it's hard to figure out how to narrow down what's going on.
Like, seriously. What is going on here?
```
error[E0599]: no method named `chars` found for reference `&str` in the current scope
   --> components/script/textinput.rs:247:19
    |
247 |     for c in text.chars() {
    |                   ^^^^^
    |
    = help: items from traits can only be used if the trait is in scope
help: trait `TextSource` which provides `chars` is implemented but not in scope; perhaps you want to import it
    |
7   + use unicode_bidi::TextSource;
    |
help: there is a method `par_chars` with a similar name
    |
247 |     for c in text.par_chars() {
    |                   ~~~~~~~~~
```
let me try
I got:
```
servo/target/debug/build/script-607bfb53410c9870/out/InterfaceTypes.rs: No such file or directory (os error 2)
   --> components/script/dom/mod.rs:209:5
    |
209 |     include!(concat!(env!("OUT_DIR"), "/InterfaceTypes.rs"));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `script` (lib) due to 1 previous error
```
on https://github.com/jdm/servo/commit/b05a845b3881f287907be101d9363b7d3500f37f. So you might need to run `mach clean` to make sure rust is not using old generated files.
Same errors after addressing that.
Usually it's good idea to start looking at first errors and fixing them first (as those can cause more false errors later).

For me these are error due to orphaning rules:
```
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/bluetoothuuid.rs:627:1
    |
627 | impl Clone for StringOrUnsignedLong {
    | ^^^^^^^^^^^^^^^--------------------
    | |              |
    | |              `script_bindings::codegen::UnionTypes::StringOrUnsignedLong` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
  --> components/script/dom/gpucanvascontext.rs:42:1
   |
42 | impl Clone for GPUCanvasConfiguration {
   | ^^^^^^^^^^^^^^^----------------------
   | |              |
   | |              `script_bindings::codegen::Bindings::WebGPUBinding::GPUCanvasConfiguration` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
  --> components/script/dom/gpucanvascontext.rs:54:1
   |
54 | impl Clone for HTMLCanvasElementOrOffscreenCanvas {
   | ^^^^^^^^^^^^^^^----------------------------------
   | |              |
   | |              `script_bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/request.rs:876:1
    |
876 | impl Clone for HeadersInit {
    | ^^^^^^^^^^^^^^^-----------
    | |              |
    | |              `script_bindings::codegen::UnionTypes::ByteStringSequenceSequenceOrByteStringByteStringRecord` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
  --> components/script/dom/gpucanvascontext.rs:63:1
   |
63 | impl malloc_size_of::MallocSizeOf for GPUTextureDescriptor {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^--------------------
   | |                                     |
   | |                                     `script_bindings::codegen::Bindings::WebGPUBinding::GPUTextureDescriptor` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
  --> components/script/dom/gpucanvascontext.rs:86:1
   |
86 | impl malloc_size_of::MallocSizeOf for HTMLCanvasElementOrOffscreenCanvas {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------------------------
   | |                                     |
   | |                                     `script_bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/security_manager.rs:170:1
    |
170 | impl Serialize for SecurityPolicyViolationEventDisposition {
    | ^^^^^^^^^^^^^^^^^^^---------------------------------------
    | |                  |
    | |                  `SecurityPolicyViolationEventDisposition` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/audiobuffersourcenode.rs:264:1
    |
264 | impl<'a> From<&'a AudioBufferSourceOptions> for AudioBufferSourceNodeOptions {
    | ^^^^^^^^^----------------------------------^^^^^----------------------------
    | |        |                                      |
    | |        |                                      `AudioBufferSourceNodeOptions` is not defined in the current crate
    | |        `script_bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/audionode.rs:370:1
    |
370 | impl From<ChannelCountMode> for ServoMediaChannelCountMode {
    | ^^^^^----------------------^^^^^--------------------------
    | |    |                          |
    | |    |                          `servo_media::servo_media_audio::node::ChannelCountMode` is not defined in the current crate
    | |    `script_bindings::codegen::Bindings::AudioNodeBinding::ChannelCountMode` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/audionode.rs:380:1
    |
380 | impl From<ChannelInterpretation> for ServoMediaChannelInterpretation {
    | ^^^^^---------------------------^^^^^-------------------------------
    | |    |                               |
    | |    |                               `servo_media::servo_media_audio::node::ChannelInterpretation` is not defined in the current crate
    | |    `script_bindings::codegen::Bindings::AudioNodeBinding::ChannelInterpretation` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/audioparam.rs:325:1
    |
325 | impl From<AutomationRate> for ParamRate {
    | ^^^^^--------------------^^^^^---------
    | |    |                        |
    | |    |                        `ParamRate` is not defined in the current crate
    | |    `AutomationRate` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead

error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
   --> components/script/dom/baseaudiocontext.rs:591:1
    |
591 | impl From<ProcessingState> for AudioContextState {
    | ^^^^^---------------------^^^^^-----------------
    | |    |                         |
    | |    |                         `AudioContextState` is not defined in the current crate
    | |    `ProcessingState` is not defined in the current crate
    | impl doesn't use only types from inside the current crate
    |
    = note: define and implement a trait or new type instead
```

is 
```
error[E0599]: no method named `chars` found for reference `&str` in the current scope
``` 
your first error?
No, I've been skipping over those orphan rule violations because I understand them. I have not found going top to bottom to be the most useful strategy so far.
> I have not found going top to bottom to be the most useful strategy so far.

Well, it the most sure ways to fix proper error properly. I got burnt few times from fixing wrong errors at wrong time.
My branch now a) compiles, b) opens pages that don't use any JS. I know what's missing that should get JS working as well.
I could help with the refactoring!

Fix patch:
diff --git a/components/script/conversions.rs b/components/script/conversions.rs
new file mode 100644
index 0000000000000..01186119ff56b
--- /dev/null
+++ b/components/script/conversions.rs
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/// A version of the Into<T> trait from the standard library that can be used
+/// to convert between two types that are not defined in the script crate.
+/// This is intended to be used on dict/enum types generated from WebIDL once
+/// those types are moved out of the script crate.
+pub trait Convert<T> {
+    fn convert(self) -> T;
+}
+
+/// A version of the TryInto<T> trait from the standard library that can be used
+/// to convert between two types that are not defined in the script crate.
+/// This is intended to be used on dict/enum types generated from WebIDL once
+/// those types are moved out of the script crate.
+pub trait TryConvert<T> {
+    type Error;
+
+    fn try_convert(self) -> Result<T, Self::Error>;
+}
diff --git a/components/script/dom/audiocontext.rs b/components/script/dom/audiocontext.rs
index bf83796674e6e..9a260617c5812 100644
--- a/components/script/dom/audiocontext.rs
+++ b/components/script/dom/audiocontext.rs
@@ -9,6 +9,7 @@ use dom_struct::dom_struct;
 use js::rust::HandleObject;
 use servo_media::audio::context::{LatencyCategory, ProcessingState, RealTimeAudioContextOptions};
 
+use crate::conversions::Convert;
 use crate::dom::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions};
 use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{
     AudioContextLatencyCategory, AudioContextMethods, AudioContextOptions, AudioTimestamp,
@@ -55,7 +56,7 @@ impl AudioContext {
     ) -> Fallible<AudioContext> {
         // Steps 1-3.
         let context = BaseAudioContext::new_inherited(
-            BaseAudioContextOptions::AudioContext(options.into()),
+            BaseAudioContextOptions::AudioContext(options.convert()),
             pipeline_id,
         )?;
 
@@ -305,9 +306,9 @@ impl AudioContextMethods<crate::DomTypeHolder> for AudioContext {
     }
 }
 
-impl From<AudioContextLatencyCategory> for LatencyCategory {
-    fn from(category: AudioContextLatencyCategory) -> Self {
-        match category {
+impl Convert<LatencyCategory> for AudioContextLatencyCategory {
+    fn convert(self) -> LatencyCategory {
+        match self {
             AudioContextLatencyCategory::Balanced => LatencyCategory::Balanced,
             AudioContextLatencyCategory::Interactive => LatencyCategory::Interactive,
             AudioContextLatencyCategory::Playback => LatencyCategory::Playback,
@@ -315,13 +316,13 @@ impl From<AudioContextLatencyCategory> for LatencyCategory {
     }
 }
 
-impl<'a> From<&'a AudioContextOptions> for RealTimeAudioContextOptions {
-    fn from(options: &AudioContextOptions) -> Self {
-        Self {
-            sample_rate: *options.sampleRate.unwrap_or(Finite::wrap(44100.)),
-            latency_hint: match options.latencyHint {
+impl<'a> Convert<RealTimeAudioContextOptions> for &'a AudioContextOptions {
+    fn convert(self) -> RealTimeAudioContextOptions {
+        RealTimeAudioContextOptions {
+            sample_rate: *self.sampleRate.unwrap_or(Finite::wrap(44100.)),
+            latency_hint: match self.latencyHint {
                 AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => {
-                    category.into()
+                    category.convert()
                 },
                 AudioContextLatencyCategoryOrDouble::Double(_) => LatencyCategory::Interactive, // TODO
             },
diff --git a/components/script/dom/audionode.rs b/components/script/dom/audionode.rs
index 91431ca29284b..65823798e5d19 100644
--- a/components/script/dom/audionode.rs
+++ b/components/script/dom/audionode.rs
@@ -11,6 +11,7 @@ use servo_media::audio::node::{
     ChannelInterpretation as ServoMediaChannelInterpretation,
 };
 
+use crate::conversions::Convert;
 use crate::dom::audioparam::AudioParam;
 use crate::dom::baseaudiocontext::BaseAudioContext;
 use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
@@ -56,8 +57,8 @@ impl AudioNode {
         }
         let ch = ChannelInfo {
             count: options.count as u8,
-            mode: options.mode.into(),
-            interpretation: options.interpretation.into(),
+            mode: options.mode.convert(),
+            interpretation: options.interpretation.convert(),
             context_channel_count: context.channel_count() as u8,
         };
         let node_id = context
@@ -339,7 +340,7 @@ impl AudioNodeMethods<crate::DomTypeHolder> for AudioNode {
         };
 
         self.channel_count_mode.set(value);
-        self.message(AudioNodeMessage::SetChannelMode(value.into()));
+        self.message(AudioNodeMessage::SetChannelMode(value.convert()));
         Ok(())
     }
 
@@ -362,14 +363,14 @@ impl AudioNodeMethods<crate::DomTypeHolder> for AudioNode {
         };
 
         self.channel_interpretation.set(value);
-        self.message(AudioNodeMessage::SetChannelInterpretation(value.into()));
+        self.message(AudioNodeMessage::SetChannelInterpretation(value.convert()));
         Ok(())
     }
 }
 
-impl From<ChannelCountMode> for ServoMediaChannelCountMode {
-    fn from(mode: ChannelCountMode) -> Self {
-        match mode {
+impl Convert<ServoMediaChannelCountMode> for ChannelCountMode {
+    fn convert(self) -> ServoMediaChannelCountMode {
+        match self {
             ChannelCountMode::Max => ServoMediaChannelCountMode::Max,
             ChannelCountMode::Clamped_max => ServoMediaChannelCountMode::ClampedMax,
             ChannelCountMode::Explicit => ServoMediaChannelCountMode::Explicit,
@@ -377,9 +378,9 @@ impl From<ChannelCountMode> for ServoMediaChannelCountMode {
     }
 }
 
-impl From<ChannelInterpretation> for ServoMediaChannelInterpretation {
-    fn from(interpretation: ChannelInterpretation) -> Self {
-        match interpretation {
+impl Convert<ServoMediaChannelInterpretation> for ChannelInterpretation {
+    fn convert(self) -> ServoMediaChannelInterpretation {
+        match self {
             ChannelInterpretation::Discrete => ServoMediaChannelInterpretation::Discrete,
             ChannelInterpretation::Speakers => ServoMediaChannelInterpretation::Speakers,
         }
diff --git a/components/script/dom/audioparam.rs b/components/script/dom/audioparam.rs
index 5ba3ef1e95133..92d0471f6f4c9 100644
--- a/components/script/dom/audioparam.rs
+++ b/components/script/dom/audioparam.rs
@@ -10,6 +10,7 @@ use servo_media::audio::graph::NodeId;
 use servo_media::audio::node::{AudioNodeMessage, AudioNodeType};
 use servo_media::audio::param::{ParamRate, ParamType, RampKind, UserAutomationEvent};
 
+use crate::conversions::Convert;
 use crate::dom::baseaudiocontext::BaseAudioContext;
 use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{
     AudioParamMethods, AutomationRate,
@@ -131,7 +132,7 @@ impl AudioParamMethods<crate::DomTypeHolder> for AudioParam {
         self.automation_rate.set(automation_rate);
         self.message_node(AudioNodeMessage::SetParamRate(
             self.param,
-            automation_rate.into(),
+            automation_rate.convert(),
         ));
 
         Ok(())
@@ -322,9 +323,9 @@ impl AudioParamMethods<crate::DomTypeHolder> for AudioParam {
 }
 
 // https://webaudio.github.io/web-audio-api/#enumdef-automationrate
-impl From<AutomationRate> for ParamRate {
-    fn from(rate: AutomationRate) -> Self {
-        match rate {
+impl Convert<ParamRate> for AutomationRate {
+    fn convert(self) -> ParamRate {
+        match self {
             AutomationRate::A_rate => ParamRate::ARate,
             AutomationRate::K_rate => ParamRate::KRate,
         }
diff --git a/components/script/dom/baseaudiocontext.rs b/components/script/dom/baseaudiocontext.rs
index 73ee5f39eb7ac..83ea5c4d5c2e7 100644
--- a/components/script/dom/baseaudiocontext.rs
+++ b/components/script/dom/baseaudiocontext.rs
@@ -21,6 +21,7 @@ use servo_media::audio::graph::NodeId;
 use servo_media::{ClientContextId, ServoMedia};
 use uuid::Uuid;
 
+use crate::conversions::Convert;
 use crate::dom::analysernode::AnalyserNode;
 use crate::dom::audiobuffer::AudioBuffer;
 use crate::dom::audiobuffersourcenode::AudioBufferSourceNode;
@@ -129,7 +130,7 @@ impl BaseAudioContext {
             ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
         let audio_context_impl = ServoMedia::get()
             .unwrap()
-            .create_audio_context(&client_context_id, options.into())
+            .create_audio_context(&client_context_id, options.convert())
             .map_err(|_| Error::NotSupported)?;
 
         Ok(BaseAudioContext {
@@ -613,9 +614,9 @@ impl BaseAudioContextMethods<crate::DomTypeHolder> for BaseAudioContext {
     }
 }
 
-impl From<BaseAudioContextOptions> for AudioContextOptions {
-    fn from(options: BaseAudioContextOptions) -> Self {
-        match options {
+impl Convert<AudioContextOptions> for BaseAudioContextOptions {
+    fn convert(self) -> AudioContextOptions {
+        match self {
             BaseAudioContextOptions::AudioContext(options) => {
                 AudioContextOptions::RealTimeAudioContext(options)
             },
@@ -626,9 +627,9 @@ impl From<BaseAudioContextOptions> for AudioContextOptions {
     }
 }
 
-impl From<ProcessingState> for AudioContextState {
-    fn from(state: ProcessingState) -> Self {
-        match state {
+impl Convert<AudioContextState> for ProcessingState {
+    fn convert(self) -> AudioContextState {
+        match self {
             ProcessingState::Suspended => AudioContextState::Suspended,
             ProcessingState::Running => AudioContextState::Running,
             ProcessingState::Closed => AudioContextState::Closed,
diff --git a/components/script/dom/biquadfilternode.rs b/components/script/dom/biquadfilternode.rs
index be0d8fdecea5a..4c319462ad680 100644
--- a/components/script/dom/biquadfilternode.rs
+++ b/components/script/dom/biquadfilternode.rs
@@ -13,6 +13,7 @@ use servo_media::audio::biquad_filter_node::{
 use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
 use servo_media::audio::param::ParamType;
 
+use crate::conversions::Convert;
 use crate::dom::audionode::AudioNode;
 use crate::dom::audioparam::AudioParam;
 use crate::dom::baseaudiocontext::BaseAudioContext;
@@ -51,7 +52,7 @@ impl BiquadFilterNode {
                 .parent
                 .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers);
         let filter = Cell::new(options.type_);
-        let options = options.into();
+        let options = options.convert();
         let node = AudioNode::new_inherited(
             AudioNodeInit::BiquadFilterNode(options),
             context,
@@ -181,26 +182,26 @@ impl BiquadFilterNodeMethods<crate::DomTypeHolder> for BiquadFilterNode {
     fn SetType(&self, filter: BiquadFilterType) {
         self.filter.set(filter);
         self.node.message(AudioNodeMessage::BiquadFilterNode(
-            BiquadFilterNodeMessage::SetFilterType(filter.into()),
+            BiquadFilterNodeMessage::SetFilterType(filter.convert()),
         ));
     }
 }
 
-impl<'a> From<&'a BiquadFilterOptions> for BiquadFilterNodeOptions {
-    fn from(options: &'a BiquadFilterOptions) -> Self {
-        Self {
-            gain: *options.gain,
-            q: *options.Q,
-            frequency: *options.frequency,
-            detune: *options.detune,
-            filter: options.type_.into(),
+impl<'a> Convert<BiquadFilterNodeOptions> for &'a BiquadFilterOptions {
+    fn convert(self) -> BiquadFilterNodeOptions {
+        BiquadFilterNodeOptions {
+            gain: *self.gain,
+            q: *self.Q,
+            frequency: *self.frequency,
+            detune: *self.detune,
+            filter: self.type_.convert(),
         }
     }
 }
 
-impl From<BiquadFilterType> for FilterType {
-    fn from(filter: BiquadFilterType) -> FilterType {
-        match filter {
+impl Convert<FilterType> for BiquadFilterType {
+    fn convert(self) -> FilterType {
+        match self {
             BiquadFilterType::Lowpass => FilterType::LowPass,
             BiquadFilterType::Highpass => FilterType::HighPass,
             BiquadFilterType::Bandpass => FilterType::BandPass,
diff --git a/components/script/dom/bluetooth.rs b/components/script/dom/bluetooth.rs
index 4d9c58a44ff28..7f27b3dc66bf6 100644
--- a/components/script/dom/bluetooth.rs
+++ b/components/script/dom/bluetooth.rs
@@ -8,6 +8,7 @@ use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
 use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence};
 use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence};
 use crate::realms::{AlreadyInRealm, InRealm};
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::{DomRefCell, Ref};
 use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothDataFilterInit;
 use crate::dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
@@ -130,7 +131,7 @@ where
                 .handle_response(response, &promise, can_gc),
             // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
             // Step 3 - 4.
-            Err(error) => promise.reject_error(Error::from(error)),
+            Err(error) => promise.reject_error(error.convert()),
         }
     }
 }
@@ -521,9 +522,9 @@ fn canonicalize_bluetooth_data_filter_init(
     Ok((data_prefix, mask))
 }
 
-impl From<BluetoothError> for Error {
-    fn from(error: BluetoothError) -> Self {
-        match error {
+impl Convert<Error> for BluetoothError {
+    fn convert(self) -> Error {
+        match self {
             BluetoothError::Type(message) => Error::Type(message),
             BluetoothError::Network => Error::Network,
             BluetoothError::NotFound => Error::NotFound,
@@ -706,7 +707,7 @@ impl PermissionAlgorithm for Bluetooth {
                 match receiver.recv().unwrap() {
                     Ok(true) => (),
                     Ok(false) => continue,
-                    Err(error) => return promise.reject_error(Error::from(error)),
+                    Err(error) => return promise.reject_error(error.convert()),
                 };
             }
 
diff --git a/components/script/dom/bluetoothdevice.rs b/components/script/dom/bluetoothdevice.rs
index 97d438210e8cf..3e44386d91095 100644
--- a/components/script/dom/bluetoothdevice.rs
+++ b/components/script/dom/bluetoothdevice.rs
@@ -14,6 +14,7 @@ use dom_struct::dom_struct;
 use ipc_channel::ipc::IpcSender;
 use profile_traits::ipc;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
 use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
@@ -249,7 +250,7 @@ impl BluetoothDevice {
                 sender,
             ))
             .unwrap();
-        receiver.recv().unwrap().map_err(Error::from)
+        receiver.recv().unwrap().map_err(Convert::convert)
     }
 }
 
diff --git a/components/script/dom/mediadeviceinfo.rs b/components/script/dom/mediadeviceinfo.rs
index 3f45f06c95f24..57ed374d9aa57 100644
--- a/components/script/dom/mediadeviceinfo.rs
+++ b/components/script/dom/mediadeviceinfo.rs
@@ -5,6 +5,7 @@
 use dom_struct::dom_struct;
 use servo_media::streams::device_monitor::MediaDeviceKind as ServoMediaDeviceKind;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::MediaDeviceInfoBinding::{
     MediaDeviceInfoMethods, MediaDeviceKind,
 };
@@ -76,9 +77,9 @@ impl MediaDeviceInfoMethods<crate::DomTypeHolder> for MediaDeviceInfo {
     }
 }
 
-impl From<ServoMediaDeviceKind> for MediaDeviceKind {
-    fn from(kind: ServoMediaDeviceKind) -> MediaDeviceKind {
-        match kind {
+impl Convert<MediaDeviceKind> for ServoMediaDeviceKind {
+    fn convert(self) -> MediaDeviceKind {
+        match self {
             ServoMediaDeviceKind::AudioInput => MediaDeviceKind::Audioinput,
             ServoMediaDeviceKind::AudioOutput => MediaDeviceKind::Audiooutput,
             ServoMediaDeviceKind::VideoInput => MediaDeviceKind::Videoinput,
diff --git a/components/script/dom/mediadevices.rs b/components/script/dom/mediadevices.rs
index 8c2283e342591..f38a4b105447f 100644
--- a/components/script/dom/mediadevices.rs
+++ b/components/script/dom/mediadevices.rs
@@ -9,6 +9,7 @@ use servo_media::streams::capture::{Constrain, ConstrainRange, MediaTrackConstra
 use servo_media::streams::MediaStreamType;
 use servo_media::ServoMedia;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::MediaDevicesBinding::{
     MediaDevicesMethods, MediaStreamConstraints,
 };
@@ -94,7 +95,7 @@ impl MediaDevicesMethods<crate::DomTypeHolder> for MediaDevices {
                     MediaDeviceInfo::new(
                         &self.global(),
                         &device.device_id,
-                        device.kind.into(),
+                        device.kind.convert(),
                         &device.label,
                         "",
                     )
diff --git a/components/script/dom/mediasession.rs b/components/script/dom/mediasession.rs
index bd4afd03d5ab6..8301c6fab5e5e 100644
--- a/components/script/dom/mediasession.rs
+++ b/components/script/dom/mediasession.rs
@@ -9,6 +9,7 @@ use embedder_traits::{MediaMetadata as EmbedderMediaMetadata, MediaSessionEvent}
 use script_traits::{MediaSessionActionType, ScriptMsg};
 
 use super::bindings::trace::HashMapTracedValues;
+use crate::conversions::Convert;
 use crate::dom::bindings::callback::ExceptionHandling;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
@@ -188,8 +189,8 @@ impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
             Some(handler) => self
                 .action_handlers
                 .borrow_mut()
-                .insert(action.into(), handler.clone()),
-            None => self.action_handlers.borrow_mut().remove(&action.into()),
+                .insert(action.convert(), handler.clone()),
+            None => self.action_handlers.borrow_mut().remove(&action.convert()),
         };
     }
 
@@ -249,9 +250,9 @@ impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
     }
 }
 
-impl From<MediaSessionAction> for MediaSessionActionType {
-    fn from(action: MediaSessionAction) -> MediaSessionActionType {
-        match action {
+impl Convert<MediaSessionActionType> for MediaSessionAction {
+    fn convert(self) -> MediaSessionActionType {
+        match self {
             MediaSessionAction::Play => MediaSessionActionType::Play,
             MediaSessionAction::Pause => MediaSessionActionType::Pause,
             MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
diff --git a/components/script/dom/oscillatornode.rs b/components/script/dom/oscillatornode.rs
index 7807eea01ab21..6cc0559cbed08 100644
--- a/components/script/dom/oscillatornode.rs
+++ b/components/script/dom/oscillatornode.rs
@@ -14,6 +14,7 @@ use servo_media::audio::oscillator_node::{
 };
 use servo_media::audio::param::ParamType;
 
+use crate::conversions::Convert;
 use crate::dom::audioparam::AudioParam;
 use crate::dom::audioscheduledsourcenode::AudioScheduledSourceNode;
 use crate::dom::baseaudiocontext::BaseAudioContext;
@@ -50,7 +51,7 @@ impl OscillatorNode {
                 .parent
                 .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers);
         let source_node = AudioScheduledSourceNode::new_inherited(
-            AudioNodeInit::OscillatorNode(options.into()),
+            AudioNodeInit::OscillatorNode(options.convert()),
             context,
             node_options,
             0, /* inputs */
@@ -150,26 +151,26 @@ impl OscillatorNodeMethods<crate::DomTypeHolder> for OscillatorNode {
         self.source_node
             .node()
             .message(AudioNodeMessage::OscillatorNode(
-                OscillatorNodeMessage::SetOscillatorType(type_.into()),
+                OscillatorNodeMessage::SetOscillatorType(type_.convert()),
             ));
         Ok(())
     }
 }
 
-impl<'a> From<&'a OscillatorOptions> for ServoMediaOscillatorOptions {
-    fn from(options: &'a OscillatorOptions) -> Self {
-        Self {
-            oscillator_type: options.type_.into(),
-            freq: *options.frequency,
-            detune: *options.detune,
+impl<'a> Convert<ServoMediaOscillatorOptions> for &'a OscillatorOptions {
+    fn convert(self) -> ServoMediaOscillatorOptions {
+        ServoMediaOscillatorOptions {
+            oscillator_type: self.type_.convert(),
+            freq: *self.frequency,
+            detune: *self.detune,
             periodic_wave_options: None, // XXX
         }
     }
 }
 
-impl From<OscillatorType> for ServoMediaOscillatorType {
-    fn from(oscillator_type: OscillatorType) -> Self {
-        match oscillator_type {
+impl Convert<ServoMediaOscillatorType> for OscillatorType {
+    fn convert(self) -> ServoMediaOscillatorType {
+        match self {
             OscillatorType::Sine => ServoMediaOscillatorType::Sine,
             OscillatorType::Square => ServoMediaOscillatorType::Square,
             OscillatorType::Sawtooth => ServoMediaOscillatorType::Sawtooth,
diff --git a/components/script/dom/pannernode.rs b/components/script/dom/pannernode.rs
index c2d558a2e731d..2912cb4a4744b 100644
--- a/components/script/dom/pannernode.rs
+++ b/components/script/dom/pannernode.rs
@@ -13,6 +13,7 @@ use servo_media::audio::panner_node::{
 };
 use servo_media::audio::param::{ParamDir, ParamType};
 
+use crate::conversions::Convert;
 use crate::dom::audionode::AudioNode;
 use crate::dom::audioparam::AudioParam;
 use crate::dom::baseaudiocontext::BaseAudioContext;
@@ -86,7 +87,7 @@ impl PannerNode {
         if *options.coneOuterGain < 0. || *options.coneOuterGain > 1. {
             return Err(Error::InvalidState);
         }
-        let options = options.into();
+        let options = options.convert();
         let node = AudioNode::new_inherited(
             AudioNodeInit::PannerNode(options),
             context,
@@ -255,7 +256,7 @@ impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
     }
     // https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel
     fn SetDistanceModel(&self, model: DistanceModelType) {
-        self.distance_model.set(model.into());
+        self.distance_model.set(model.convert());
         let msg = PannerNodeMessage::SetDistanceModel(self.distance_model.get());
         self.upcast::<AudioNode>()
             .message(AudioNodeMessage::PannerNode(msg));
@@ -269,7 +270,7 @@ impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
     }
     // https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel
     fn SetPanningModel(&self, model: PanningModelType) {
-        self.panning_model.set(model.into());
+        self.panning_model.set(model.convert());
         let msg = PannerNodeMessage::SetPanningModel(self.panning_model.get());
         self.upcast::<AudioNode>()
             .message(AudioNodeMessage::PannerNode(msg));
@@ -372,30 +373,30 @@ impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
     }
 }
 
-impl<'a> From<&'a PannerOptions> for PannerNodeOptions {
-    fn from(options: &'a PannerOptions) -> Self {
-        Self {
-            panning_model: options.panningModel.into(),
-            distance_model: options.distanceModel.into(),
-            position_x: *options.positionX,
-            position_y: *options.positionY,
-            position_z: *options.positionZ,
-            orientation_x: *options.orientationX,
-            orientation_y: *options.orientationY,
-            orientation_z: *options.orientationZ,
-            ref_distance: *options.refDistance,
-            max_distance: *options.maxDistance,
-            rolloff_factor: *options.rolloffFactor,
-            cone_inner_angle: *options.coneInnerAngle,
-            cone_outer_angle: *options.coneOuterAngle,
-            cone_outer_gain: *options.coneOuterGain,
+impl<'a> Convert<PannerNodeOptions> for &'a PannerOptions {
+    fn convert(self) -> PannerNodeOptions {
+        PannerNodeOptions {
+            panning_model: self.panningModel.convert(),
+            distance_model: self.distanceModel.convert(),
+            position_x: *self.positionX,
+            position_y: *self.positionY,
+            position_z: *self.positionZ,
+            orientation_x: *self.orientationX,
+            orientation_y: *self.orientationY,
+            orientation_z: *self.orientationZ,
+            ref_distance: *self.refDistance,
+            max_distance: *self.maxDistance,
+            rolloff_factor: *self.rolloffFactor,
+            cone_inner_angle: *self.coneInnerAngle,
+            cone_outer_angle: *self.coneOuterAngle,
+            cone_outer_gain: *self.coneOuterGain,
         }
     }
 }
 
-impl From<DistanceModelType> for DistanceModel {
-    fn from(model: DistanceModelType) -> Self {
-        match model {
+impl Convert<DistanceModel> for DistanceModelType {
+    fn convert(self) -> DistanceModel {
+        match self {
             DistanceModelType::Linear => DistanceModel::Linear,
             DistanceModelType::Inverse => DistanceModel::Inverse,
             DistanceModelType::Exponential => DistanceModel::Exponential,
@@ -403,9 +404,9 @@ impl From<DistanceModelType> for DistanceModel {
     }
 }
 
-impl From<PanningModelType> for PanningModel {
-    fn from(model: PanningModelType) -> Self {
-        match model {
+impl Convert<PanningModel> for PanningModelType {
+    fn convert(self) -> PanningModel {
+        match self {
             PanningModelType::Equalpower => PanningModel::EqualPower,
             PanningModelType::HRTF => PanningModel::HRTF,
         }
diff --git a/components/script/dom/permissions.rs b/components/script/dom/permissions.rs
index 94698261093e2..c2a590f997ed2 100644
--- a/components/script/dom/permissions.rs
+++ b/components/script/dom/permissions.rs
@@ -12,6 +12,7 @@ use js::jsapi::JSObject;
 use js::jsval::{ObjectValue, UndefinedValue};
 use servo_config::pref;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
     PermissionDescriptor, PermissionName, PermissionState, PermissionStatusMethods,
 };
@@ -251,8 +252,7 @@ impl PermissionAlgorithm for Permissions {
             // Step 3.
             PermissionState::Prompt => {
                 let perm_name = status.get_query();
-                let prompt =
-                    PermissionPrompt::Request(embedder_traits::PermissionName::from(perm_name));
+                let prompt = PermissionPrompt::Request(perm_name.convert());
 
                 // https://w3c.github.io/permissions/#request-permission-to-use (Step 3 - 4)
                 let globalscope = GlobalScope::current().expect("No current global object");
@@ -305,7 +305,7 @@ pub fn get_descriptor_permission_state(
             .borrow_mut()
             .remove(&permission_name.to_string());
         prompt_user_from_embedder(
-            PermissionPrompt::Insecure(embedder_traits::PermissionName::from(permission_name)),
+            PermissionPrompt::Insecure(permission_name.convert()),
             &globalscope,
         )
     };
@@ -374,9 +374,9 @@ fn prompt_user_from_embedder(prompt: PermissionPrompt, gs: &GlobalScope) -> Perm
     }
 }
 
-impl From<PermissionName> for embedder_traits::PermissionName {
-    fn from(permission_name: PermissionName) -> Self {
-        match permission_name {
+impl Convert<embedder_traits::PermissionName> for PermissionName {
+    fn convert(self) -> embedder_traits::PermissionName {
+        match self {
             PermissionName::Geolocation => embedder_traits::PermissionName::Geolocation,
             PermissionName::Notifications => embedder_traits::PermissionName::Notifications,
             PermissionName::Push => embedder_traits::PermissionName::Push,
diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs
index 4a39589afd694..4d030847d186a 100644
--- a/components/script/dom/request.rs
+++ b/components/script/dom/request.rs
@@ -24,6 +24,7 @@ use net_traits::ReferrerPolicy as MsgReferrerPolicy;
 use servo_url::ServoUrl;
 
 use crate::body::{consume_body, BodyMixin, BodyType, Extractable};
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
 use crate::dom::bindings::codegen::Bindings::RequestBinding::{
@@ -302,12 +303,12 @@ impl RequestMethods<crate::DomTypeHolder> for Request {
 
         // Step 15
         if let Some(init_referrerpolicy) = init.referrerPolicy.as_ref() {
-            let init_referrer_policy = (*init_referrerpolicy).into();
+            let init_referrer_policy = (*init_referrerpolicy).convert();
             request.referrer_policy = init_referrer_policy;
         }
 
         // Step 16
-        let mode = init.mode.as_ref().map(|m| (*m).into()).or(fallback_mode);
+        let mode = init.mode.as_ref().map(|m| (*m).convert()).or(fallback_mode);
 
         // Step 17
         if let Some(NetTraitsRequestMode::Navigate) = mode {
@@ -321,13 +322,13 @@ impl RequestMethods<crate::DomTypeHolder> for Request {
 
         // Step 19
         if let Some(init_credentials) = init.credentials.as_ref() {
-            let credentials = (*init_credentials).into();
+            let credentials = (*init_credentials).convert();
             request.credentials_mode = credentials;
         }
 
         // Step 20
         if let Some(init_cache) = init.cache.as_ref() {
-            let cache = (*init_cache).into();
+            let cache = (*init_cache).convert();
             request.cache_mode = cache;
         }
 
@@ -342,7 +343,7 @@ impl RequestMethods<crate::DomTypeHolder> for Request {
 
         // Step 22
         if let Some(init_redirect) = init.redirect.as_ref() {
-            let redirect = (*init_redirect).into();
+            let redirect = (*init_redirect).convert();
             request.redirect_mode = redirect;
         }
 
@@ -546,7 +547,7 @@ impl RequestMethods<crate::DomTypeHolder> for Request {
 
     // https://fetch.spec.whatwg.org/#dom-request-destination
     fn Destination(&self) -> RequestDestination {
-        self.request.borrow().destination.into()
+        self.request.borrow().destination.convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-referrer
@@ -564,30 +565,30 @@ impl RequestMethods<crate::DomTypeHolder> for Request {
 
     // https://fetch.spec.whatwg.org/#dom-request-referrerpolicy
     fn ReferrerPolicy(&self) -> ReferrerPolicy {
-        self.request.borrow().referrer_policy.into()
+        self.request.borrow().referrer_policy.convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-mode
     fn Mode(&self) -> RequestMode {
-        self.request.borrow().mode.clone().into()
+        self.request.borrow().mode.clone().convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-credentials
     fn Credentials(&self) -> RequestCredentials {
         let r = self.request.borrow().clone();
-        r.credentials_mode.into()
+        r.credentials_mode.convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-cache
     fn Cache(&self) -> RequestCache {
         let r = self.request.borrow().clone();
-        r.cache_mode.into()
+        r.cache_mode.convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-redirect
     fn Redirect(&self) -> RequestRedirect {
         let r = self.request.borrow().clone();
-        r.redirect_mode.into()
+        r.redirect_mode.convert()
     }
 
     // https://fetch.spec.whatwg.org/#dom-request-integrity
@@ -669,9 +670,9 @@ impl BodyMixin for Request {
     }
 }
 
-impl From<RequestCache> for NetTraitsRequestCache {
-    fn from(cache: RequestCache) -> Self {
-        match cache {
+impl Convert<NetTraitsRequestCache> for RequestCache {
+    fn convert(self) -> NetTraitsRequestCache {
+        match self {
             RequestCache::Default => NetTraitsRequestCache::Default,
             RequestCache::No_store => NetTraitsRequestCache::NoStore,
             RequestCache::Reload => NetTraitsRequestCache::Reload,
@@ -682,9 +683,9 @@ impl From<RequestCache> for NetTraitsRequestCache {
     }
 }
 
-impl From<NetTraitsRequestCache> for RequestCache {
-    fn from(cache: NetTraitsRequestCache) -> Self {
-        match cache {
+impl Convert<RequestCache> for NetTraitsRequestCache {
+    fn convert(self) -> RequestCache {
+        match self {
             NetTraitsRequestCache::Default => RequestCache::Default,
             NetTraitsRequestCache::NoStore => RequestCache::No_store,
             NetTraitsRequestCache::Reload => RequestCache::Reload,
@@ -695,9 +696,9 @@ impl From<NetTraitsRequestCache> for RequestCache {
     }
 }
 
-impl From<RequestCredentials> for NetTraitsRequestCredentials {
-    fn from(credentials: RequestCredentials) -> Self {
-        match credentials {
+impl Convert<NetTraitsRequestCredentials> for RequestCredentials {
+    fn convert(self) -> NetTraitsRequestCredentials {
+        match self {
             RequestCredentials::Omit => NetTraitsRequestCredentials::Omit,
             RequestCredentials::Same_origin => NetTraitsRequestCredentials::CredentialsSameOrigin,
             RequestCredentials::Include => NetTraitsRequestCredentials::Include,
@@ -705,9 +706,9 @@ impl From<RequestCredentials> for NetTraitsRequestCredentials {
     }
 }
 
-impl From<NetTraitsRequestCredentials> for RequestCredentials {
-    fn from(credentials: NetTraitsRequestCredentials) -> Self {
-        match credentials {
+impl Convert<RequestCredentials> for NetTraitsRequestCredentials {
+    fn convert(self) -> RequestCredentials {
+        match self {
             NetTraitsRequestCredentials::Omit => RequestCredentials::Omit,
             NetTraitsRequestCredentials::CredentialsSameOrigin => RequestCredentials::Same_origin,
             NetTraitsRequestCredentials::Include => RequestCredentials::Include,
@@ -715,9 +716,9 @@ impl From<NetTraitsRequestCredentials> for RequestCredentials {
     }
 }
 
-impl From<RequestDestination> for NetTraitsRequestDestination {
-    fn from(destination: RequestDestination) -> Self {
-        match destination {
+impl Convert<NetTraitsRequestDestination> for RequestDestination {
+    fn convert(self) -> NetTraitsRequestDestination {
+        match self {
             RequestDestination::_empty => NetTraitsRequestDestination::None,
             RequestDestination::Audio => NetTraitsRequestDestination::Audio,
             RequestDestination::Document => NetTraitsRequestDestination::Document,
@@ -741,9 +742,9 @@ impl From<RequestDestination> for NetTraitsRequestDestination {
     }
 }
 
-impl From<NetTraitsRequestDestination> for RequestDestination {
-    fn from(destination: NetTraitsRequestDestination) -> Self {
-        match destination {
+impl Convert<RequestDestination> for NetTraitsRequestDestination {
+    fn convert(self) -> RequestDestination {
+        match self {
             NetTraitsRequestDestination::None => RequestDestination::_empty,
             NetTraitsRequestDestination::Audio => RequestDestination::Audio,
             NetTraitsRequestDestination::Document => RequestDestination::Document,
@@ -773,9 +774,9 @@ impl From<NetTraitsRequestDestination> for RequestDestination {
     }
 }
 
-impl From<RequestMode> for NetTraitsRequestMode {
-    fn from(mode: RequestMode) -> Self {
-        match mode {
+impl Convert<NetTraitsRequestMode> for RequestMode {
+    fn convert(self) -> NetTraitsRequestMode {
+        match self {
             RequestMode::Navigate => NetTraitsRequestMode::Navigate,
             RequestMode::Same_origin => NetTraitsRequestMode::SameOrigin,
             RequestMode::No_cors => NetTraitsRequestMode::NoCors,
@@ -784,9 +785,9 @@ impl From<RequestMode> for NetTraitsRequestMode {
     }
 }
 
-impl From<NetTraitsRequestMode> for RequestMode {
-    fn from(mode: NetTraitsRequestMode) -> Self {
-        match mode {
+impl Convert<RequestMode> for NetTraitsRequestMode {
+    fn convert(self) -> RequestMode {
+        match self {
             NetTraitsRequestMode::Navigate => RequestMode::Navigate,
             NetTraitsRequestMode::SameOrigin => RequestMode::Same_origin,
             NetTraitsRequestMode::NoCors => RequestMode::No_cors,
@@ -798,9 +799,9 @@ impl From<NetTraitsRequestMode> for RequestMode {
     }
 }
 
-impl From<ReferrerPolicy> for MsgReferrerPolicy {
-    fn from(policy: ReferrerPolicy) -> Self {
-        match policy {
+impl Convert<MsgReferrerPolicy> for ReferrerPolicy {
+    fn convert(self) -> MsgReferrerPolicy {
+        match self {
             ReferrerPolicy::_empty => MsgReferrerPolicy::EmptyString,
             ReferrerPolicy::No_referrer => MsgReferrerPolicy::NoReferrer,
             ReferrerPolicy::No_referrer_when_downgrade => {
@@ -818,9 +819,9 @@ impl From<ReferrerPolicy> for MsgReferrerPolicy {
     }
 }
 
-impl From<MsgReferrerPolicy> for ReferrerPolicy {
-    fn from(policy: MsgReferrerPolicy) -> Self {
-        match policy {
+impl Convert<ReferrerPolicy> for MsgReferrerPolicy {
+    fn convert(self) -> ReferrerPolicy {
+        match self {
             MsgReferrerPolicy::EmptyString => ReferrerPolicy::_empty,
             MsgReferrerPolicy::NoReferrer => ReferrerPolicy::No_referrer,
             MsgReferrerPolicy::NoReferrerWhenDowngrade => {
@@ -838,9 +839,9 @@ impl From<MsgReferrerPolicy> for ReferrerPolicy {
     }
 }
 
-impl From<RequestRedirect> for NetTraitsRequestRedirect {
-    fn from(redirect: RequestRedirect) -> Self {
-        match redirect {
+impl Convert<NetTraitsRequestRedirect> for RequestRedirect {
+    fn convert(self) -> NetTraitsRequestRedirect {
+        match self {
             RequestRedirect::Follow => NetTraitsRequestRedirect::Follow,
             RequestRedirect::Error => NetTraitsRequestRedirect::Error,
             RequestRedirect::Manual => NetTraitsRequestRedirect::Manual,
@@ -848,9 +849,9 @@ impl From<RequestRedirect> for NetTraitsRequestRedirect {
     }
 }
 
-impl From<NetTraitsRequestRedirect> for RequestRedirect {
-    fn from(redirect: NetTraitsRequestRedirect) -> Self {
-        match redirect {
+impl Convert<RequestRedirect> for NetTraitsRequestRedirect {
+    fn convert(self) -> RequestRedirect {
+        match self {
             NetTraitsRequestRedirect::Follow => RequestRedirect::Follow,
             NetTraitsRequestRedirect::Error => RequestRedirect::Error,
             NetTraitsRequestRedirect::Manual => RequestRedirect::Manual,
diff --git a/components/script/dom/rtcdatachannel.rs b/components/script/dom/rtcdatachannel.rs
index 0c5ca6e3abf5e..dd8c65ed8314e 100644
--- a/components/script/dom/rtcdatachannel.rs
+++ b/components/script/dom/rtcdatachannel.rs
@@ -16,6 +16,7 @@ use servo_media::webrtc::{
     DataChannelId, DataChannelInit, DataChannelMessage, DataChannelState, WebRtcError,
 };
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::{
     RTCDataChannelInit, RTCDataChannelMethods, RTCDataChannelState,
@@ -61,7 +62,7 @@ impl RTCDataChannel {
         options: &RTCDataChannelInit,
         servo_media_id: Option<DataChannelId>,
     ) -> RTCDataChannel {
-        let mut init: DataChannelInit = options.into();
+        let mut init: DataChannelInit = options.convert();
         init.label = label.to_string();
 
         let controller = peer_connection.get_webrtc_controller().borrow();
@@ -215,7 +216,7 @@ impl RTCDataChannel {
             );
             event.upcast::<Event>().fire(self.upcast(), can_gc);
         };
-        self.ready_state.set(state.into());
+        self.ready_state.set(state.convert());
     }
 
     fn send(&self, source: &SendSource) -> Fallible<()> {
@@ -364,23 +365,23 @@ impl RTCDataChannelMethods<crate::DomTypeHolder> for RTCDataChannel {
     }
 }
 
-impl From<&RTCDataChannelInit> for DataChannelInit {
-    fn from(init: &RTCDataChannelInit) -> DataChannelInit {
+impl Convert<DataChannelInit> for &RTCDataChannelInit {
+    fn convert(self) -> DataChannelInit {
         DataChannelInit {
             label: String::new(),
-            id: init.id,
-            max_packet_life_time: init.maxPacketLifeTime,
-            max_retransmits: init.maxRetransmits,
-            negotiated: init.negotiated,
-            ordered: init.ordered,
-            protocol: init.protocol.to_string(),
+            id: self.id,
+            max_packet_life_time: self.maxPacketLifeTime,
+            max_retransmits: self.maxRetransmits,
+            negotiated: self.negotiated,
+            ordered: self.ordered,
+            protocol: self.protocol.to_string(),
         }
     }
 }
 
-impl From<DataChannelState> for RTCDataChannelState {
-    fn from(state: DataChannelState) -> RTCDataChannelState {
-        match state {
+impl Convert<RTCDataChannelState> for DataChannelState {
+    fn convert(self) -> RTCDataChannelState {
+        match self {
             DataChannelState::Connecting | DataChannelState::__Unknown(_) => {
                 RTCDataChannelState::Connecting
             },
diff --git a/components/script/dom/rtcpeerconnection.rs b/components/script/dom/rtcpeerconnection.rs
index 60b4013527328..8d7fbc4303a73 100644
--- a/components/script/dom/rtcpeerconnection.rs
+++ b/components/script/dom/rtcpeerconnection.rs
@@ -17,6 +17,7 @@ use servo_media::webrtc::{
 };
 use servo_media::ServoMedia;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelInit;
 use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateInit;
@@ -372,7 +373,7 @@ impl RTCPeerConnection {
         }
 
         // step 2 (state derivation already done by gstreamer)
-        let state: RTCIceGatheringState = state.into();
+        let state: RTCIceGatheringState = state.convert();
 
         // step 3
         if state == self.gathering_state.get() {
@@ -414,7 +415,7 @@ impl RTCPeerConnection {
         }
 
         // step 2 (state derivation already done by gstreamer)
-        let state: RTCIceConnectionState = state.into();
+        let state: RTCIceConnectionState = state.convert();
 
         // step 3
         if state == self.ice_connection_state.get() {
@@ -440,7 +441,7 @@ impl RTCPeerConnection {
             return;
         }
 
-        let state: RTCSignalingState = state.into();
+        let state: RTCSignalingState = state.convert();
 
         if state == self.signaling_state.get() {
             return;
@@ -479,7 +480,7 @@ impl RTCPeerConnection {
                             // create a fresh one
                             this.create_offer();
                         } else {
-                            let init: RTCSessionDescriptionInit = desc.into();
+                            let init: RTCSessionDescriptionInit = desc.convert();
                             for promise in this.offer_promises.borrow_mut().drain(..) {
                                 promise.resolve_native(&init);
                             }
@@ -511,7 +512,7 @@ impl RTCPeerConnection {
                             // create a fresh one
                             this.create_answer();
                         } else {
-                            let init: RTCSessionDescriptionInit = desc.into();
+                            let init: RTCSessionDescriptionInit = desc.convert();
                             for promise in this.answer_promises.borrow_mut().drain(..) {
                                 promise.resolve_native(&init);
                             }
@@ -665,7 +666,7 @@ impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
         // XXXManishearth validate the current state
         let p = Promise::new_in_current_realm(comp, can_gc);
         let this = Trusted::new(self);
-        let desc: SessionDescription = desc.into();
+        let desc: SessionDescription = desc.convert();
         let trusted_promise = TrustedPromise::new(p.clone());
         let (task_source, canceller) = self
             .global()
@@ -684,7 +685,7 @@ impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
                             // XXXManishearth spec actually asks for an intricate
                             // dance between pending/current local/remote descriptions
                             let this = this.root();
-                            let desc = desc.into();
+                            let desc = desc.convert();
                             let desc = RTCSessionDescription::Constructor(
                                 this.global().as_window(),
                                 None,
@@ -711,7 +712,7 @@ impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
         // XXXManishearth validate the current state
         let p = Promise::new_in_current_realm(comp, can_gc);
         let this = Trusted::new(self);
-        let desc: SessionDescription = desc.into();
+        let desc: SessionDescription = desc.convert();
         let trusted_promise = TrustedPromise::new(p.clone());
         let (task_source, canceller) = self
             .global()
@@ -730,7 +731,7 @@ impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
                             // XXXManishearth spec actually asks for an intricate
                             // dance between pending/current local/remote descriptions
                             let this = this.root();
-                            let desc = desc.into();
+                            let desc = desc.convert();
                             let desc = RTCSessionDescription::Constructor(
                                 this.global().as_window(),
                                 None,
@@ -822,9 +823,9 @@ impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
     }
 }
 
-impl From<SessionDescription> for RTCSessionDescriptionInit {
-    fn from(desc: SessionDescription) -> Self {
-        let type_ = match desc.type_ {
+impl Convert<RTCSessionDescriptionInit> for SessionDescription {
+    fn convert(self) -> RTCSessionDescriptionInit {
+        let type_ = match self.type_ {
             SdpType::Answer => RTCSdpType::Answer,
             SdpType::Offer => RTCSdpType::Offer,
             SdpType::Pranswer => RTCSdpType::Pranswer,
@@ -832,14 +833,14 @@ impl From<SessionDescription> for RTCSessionDescriptionInit {
         };
         RTCSessionDescriptionInit {
             type_,
-            sdp: desc.sdp.into(),
+            sdp: self.sdp.into(),
         }
     }
 }
 
-impl<'a> From<&'a RTCSessionDescriptionInit> for SessionDescription {
-    fn from(desc: &'a RTCSessionDescriptionInit) -> Self {
-        let type_ = match desc.type_ {
+impl<'a> Convert<SessionDescription> for &'a RTCSessionDescriptionInit {
+    fn convert(self) -> SessionDescription {
+        let type_ = match self.type_ {
             RTCSdpType::Answer => SdpType::Answer,
             RTCSdpType::Offer => SdpType::Offer,
             RTCSdpType::Pranswer => SdpType::Pranswer,
@@ -847,14 +848,14 @@ impl<'a> From<&'a RTCSessionDescriptionInit> for SessionDescription {
         };
         SessionDescription {
             type_,
-            sdp: desc.sdp.to_string(),
+            sdp: self.sdp.to_string(),
         }
     }
 }
 
-impl From<GatheringState> for RTCIceGatheringState {
-    fn from(state: GatheringState) -> Self {
-        match state {
+impl Convert<RTCIceGatheringState> for GatheringState {
+    fn convert(self) -> RTCIceGatheringState {
+        match self {
             GatheringState::New => RTCIceGatheringState::New,
             GatheringState::Gathering => RTCIceGatheringState::Gathering,
             GatheringState::Complete => RTCIceGatheringState::Complete,
@@ -862,9 +863,9 @@ impl From<GatheringState> for RTCIceGatheringState {
     }
 }
 
-impl From<IceConnectionState> for RTCIceConnectionState {
-    fn from(state: IceConnectionState) -> Self {
-        match state {
+impl Convert<RTCIceConnectionState> for IceConnectionState {
+    fn convert(self) -> RTCIceConnectionState {
+        match self {
             IceConnectionState::New => RTCIceConnectionState::New,
             IceConnectionState::Checking => RTCIceConnectionState::Checking,
             IceConnectionState::Connected => RTCIceConnectionState::Connected,
@@ -876,9 +877,9 @@ impl From<IceConnectionState> for RTCIceConnectionState {
     }
 }
 
-impl From<SignalingState> for RTCSignalingState {
-    fn from(state: SignalingState) -> Self {
-        match state {
+impl Convert<RTCSignalingState> for SignalingState {
+    fn convert(self) -> RTCSignalingState {
+        match self {
             SignalingState::Stable => RTCSignalingState::Stable,
             SignalingState::HaveLocalOffer => RTCSignalingState::Have_local_offer,
             SignalingState::HaveRemoteOffer => RTCSignalingState::Have_remote_offer,
diff --git a/components/script/dom/webgpu/gpubindgroup.rs b/components/script/dom/webgpu/gpubindgroup.rs
index b734684b92d49..bb698210c9394 100644
--- a/components/script/dom/webgpu/gpubindgroup.rs
+++ b/components/script/dom/webgpu/gpubindgroup.rs
@@ -8,6 +8,7 @@ use dom_struct::dom_struct;
 use webgpu::wgc::binding_model::BindGroupDescriptor;
 use webgpu::{WebGPU, WebGPUBindGroup, WebGPUDevice, WebGPURequest};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUBindGroupDescriptor, GPUBindGroupMethods,
@@ -81,11 +82,11 @@ impl GPUBindGroup {
         let entries = descriptor
             .entries
             .iter()
-            .map(|bind| bind.into())
+            .map(|bind| bind.convert())
             .collect::<Vec<_>>();
 
         let desc = BindGroupDescriptor {
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             layout: descriptor.layout.id().0,
             entries: Cow::Owned(entries),
         };
diff --git a/components/script/dom/webgpu/gpubindgrouplayout.rs b/components/script/dom/webgpu/gpubindgrouplayout.rs
index bdfdcab1e9e36..470b24c3192e5 100644
--- a/components/script/dom/webgpu/gpubindgrouplayout.rs
+++ b/components/script/dom/webgpu/gpubindgrouplayout.rs
@@ -8,6 +8,7 @@ use dom_struct::dom_struct;
 use webgpu::wgc::binding_model::BindGroupLayoutDescriptor;
 use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPURequest};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUBindGroupLayoutDescriptor, GPUBindGroupLayoutMethods,
@@ -80,7 +81,7 @@ impl GPUBindGroupLayout {
 
         let desc = match entries {
             Ok(entries) => Some(BindGroupLayoutDescriptor {
-                label: (&descriptor.parent).into(),
+                label: (&descriptor.parent).convert(),
                 entries: Cow::Owned(entries),
             }),
             Err(error) => {
diff --git a/components/script/dom/webgpu/gpubuffer.rs b/components/script/dom/webgpu/gpubuffer.rs
index 6a47b14331fd7..2a546a5e8496a 100644
--- a/components/script/dom/webgpu/gpubuffer.rs
+++ b/components/script/dom/webgpu/gpubuffer.rs
@@ -12,6 +12,7 @@ use js::typedarray::ArrayBuffer;
 use webgpu::wgc::device::HostMap;
 use webgpu::{wgt, Mapping, WebGPU, WebGPUBuffer, WebGPURequest, WebGPUResponse};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::buffer_source::DataBlock;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
@@ -136,7 +137,7 @@ impl GPUBuffer {
         descriptor: &GPUBufferDescriptor,
     ) -> Fallible<DomRoot<GPUBuffer>> {
         let desc = wgt::BufferDescriptor {
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             size: descriptor.size as wgt::BufferAddress,
             usage: wgt::BufferUsages::from_bits_retain(descriptor.usage),
             mapped_at_creation: descriptor.mappedAtCreation,
diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs
index 06c69190a091f..3fb906e258148 100644
--- a/components/script/dom/webgpu/gpucanvascontext.rs
+++ b/components/script/dom/webgpu/gpucanvascontext.rs
@@ -20,6 +20,7 @@ use webrender_api::ImageKey;
 
 use super::gpuconvert::convert_texture_descriptor;
 use super::gputexture::GPUTexture;
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
@@ -196,7 +197,7 @@ impl GPUCanvasContext {
             drawing_buffer.config = Some(ContextConfiguration {
                 device_id: configuration.device.id().0,
                 queue_id: configuration.device.queue_id().0,
-                format: configuration.format.into(),
+                format: configuration.format.convert(),
                 is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
             });
         } else {
diff --git a/components/script/dom/webgpu/gpucommandencoder.rs b/components/script/dom/webgpu/gpucommandencoder.rs
index 8c72b12f6b70a..db92794eaf2b6 100644
--- a/components/script/dom/webgpu/gpucommandencoder.rs
+++ b/components/script/dom/webgpu/gpucommandencoder.rs
@@ -9,6 +9,7 @@ use webgpu::{
     WebGPURenderPass, WebGPURequest,
 };
 
+use crate::conversions::{Convert, TryConvert};
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUCommandBufferDescriptor, GPUCommandEncoderDescriptor, GPUCommandEncoderMethods,
@@ -93,7 +94,7 @@ impl GPUCommandEncoder {
                 device_id: device.id().0,
                 command_encoder_id,
                 desc: wgt::CommandEncoderDescriptor {
-                    label: (&descriptor.parent).into(),
+                    label: (&descriptor.parent).convert(),
                 },
             })
             .expect("Failed to create WebGPU command encoder");
@@ -131,7 +132,7 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
         if let Err(e) = self.channel.0.send(WebGPURequest::BeginComputePass {
             command_encoder_id: self.id().0,
             compute_pass_id,
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             device_id: self.device.id().0,
         }) {
             warn!("Failed to send WebGPURequest::BeginComputePass {e:?}");
@@ -179,7 +180,7 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
                     clear_value: color
                         .clearValue
                         .as_ref()
-                        .map(|color| (color).try_into())
+                        .map(|color| (color).try_convert())
                         .transpose()?
                         .unwrap_or_default(),
                     read_only: false,
@@ -196,7 +197,7 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
         if let Err(e) = self.channel.0.send(WebGPURequest::BeginRenderPass {
             command_encoder_id: self.id().0,
             render_pass_id,
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             depth_stencil_attachment,
             color_attachments,
             device_id: self.device.id().0,
@@ -246,9 +247,9 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
             .0
             .send(WebGPURequest::CopyBufferToTexture {
                 command_encoder_id: self.encoder.0,
-                source: source.into(),
-                destination: destination.try_into()?,
-                copy_size: (&copy_size).try_into()?,
+                source: source.convert(),
+                destination: destination.try_convert()?,
+                copy_size: (&copy_size).try_convert()?,
             })
             .expect("Failed to send CopyBufferToTexture");
 
@@ -266,9 +267,9 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
             .0
             .send(WebGPURequest::CopyTextureToBuffer {
                 command_encoder_id: self.encoder.0,
-                source: source.try_into()?,
-                destination: destination.into(),
-                copy_size: (&copy_size).try_into()?,
+                source: source.try_convert()?,
+                destination: destination.convert(),
+                copy_size: (&copy_size).try_convert()?,
             })
             .expect("Failed to send CopyTextureToBuffer");
 
@@ -286,9 +287,9 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
             .0
             .send(WebGPURequest::CopyTextureToTexture {
                 command_encoder_id: self.encoder.0,
-                source: source.try_into()?,
-                destination: destination.try_into()?,
-                copy_size: (&copy_size).try_into()?,
+                source: source.try_convert()?,
+                destination: destination.try_convert()?,
+                copy_size: (&copy_size).try_convert()?,
             })
             .expect("Failed to send CopyTextureToTexture");
 
@@ -303,7 +304,7 @@ impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder {
                 command_encoder_id: self.encoder.0,
                 device_id: self.device.id().0,
                 desc: wgt::CommandBufferDescriptor {
-                    label: (&descriptor.parent).into(),
+                    label: (&descriptor.parent).convert(),
                 },
             })
             .expect("Failed to send Finish");
diff --git a/components/script/dom/webgpu/gpucomputepipeline.rs b/components/script/dom/webgpu/gpucomputepipeline.rs
index ace43db11b4af..a5d0997e3a3d6 100644
--- a/components/script/dom/webgpu/gpucomputepipeline.rs
+++ b/components/script/dom/webgpu/gpucomputepipeline.rs
@@ -7,6 +7,7 @@ use ipc_channel::ipc::IpcSender;
 use webgpu::wgc::pipeline::ComputePipelineDescriptor;
 use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPUComputePipeline, WebGPURequest, WebGPUResponse};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUComputePipelineDescriptor, GPUComputePipelineMethods,
@@ -79,9 +80,9 @@ impl GPUComputePipeline {
         let pipeline_layout = device.get_pipeline_layout_data(&descriptor.parent.layout);
 
         let desc = ComputePipelineDescriptor {
-            label: (&descriptor.parent.parent).into(),
+            label: (&descriptor.parent.parent).convert(),
             layout: pipeline_layout.explicit(),
-            stage: (&descriptor.compute).into(),
+            stage: (&descriptor.compute).convert(),
             cache: None,
         };
 
diff --git a/components/script/dom/webgpu/gpuconvert.rs b/components/script/dom/webgpu/gpuconvert.rs
index db2390a03b014..bcb83ee80b549 100644
--- a/components/script/dom/webgpu/gpuconvert.rs
+++ b/components/script/dom/webgpu/gpuconvert.rs
@@ -11,6 +11,7 @@ use webgpu::wgc::pipeline::ProgrammableStageDescriptor;
 use webgpu::wgc::resource::TextureDescriptor;
 use webgpu::wgt::{self, AstcBlock, AstcChannel};
 
+use crate::conversions::{Convert, TryConvert};
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUAddressMode, GPUBindGroupEntry, GPUBindGroupLayoutEntry, GPUBindingResource,
     GPUBlendComponent, GPUBlendFactor, GPUBlendOperation, GPUBufferBindingType, GPUColor,
@@ -24,9 +25,9 @@ use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
 use crate::dom::bindings::error::{Error, Fallible};
 use crate::dom::types::GPUDevice;
 
-impl From<GPUTextureFormat> for wgt::TextureFormat {
-    fn from(format: GPUTextureFormat) -> Self {
-        match format {
+impl Convert<wgt::TextureFormat> for GPUTextureFormat {
+    fn convert(self) -> wgt::TextureFormat {
+        match self {
             GPUTextureFormat::R8unorm => wgt::TextureFormat::R8Unorm,
             GPUTextureFormat::R8snorm => wgt::TextureFormat::R8Snorm,
             GPUTextureFormat::R8uint => wgt::TextureFormat::R8Uint,
@@ -210,11 +211,11 @@ impl From<GPUTextureFormat> for wgt::TextureFormat {
     }
 }
 
-impl TryFrom<&GPUExtent3D> for wgt::Extent3d {
+impl TryConvert<wgt::Extent3d> for &GPUExtent3D {
     type Error = Error;
 
-    fn try_from(size: &GPUExtent3D) -> Result<Self, Self::Error> {
-        match *size {
+    fn try_convert(self) -> Result<wgt::Extent3d, Self::Error> {
+        match *self {
             GPUExtent3D::GPUExtent3DDict(ref dict) => Ok(wgt::Extent3d {
                 width: dict.width,
                 height: dict.height,
@@ -238,19 +239,19 @@ impl TryFrom<&GPUExtent3D> for wgt::Extent3d {
     }
 }
 
-impl From<&GPUImageDataLayout> for wgt::ImageDataLayout {
-    fn from(data_layout: &GPUImageDataLayout) -> Self {
+impl Convert<wgt::ImageDataLayout> for &GPUImageDataLayout {
+    fn convert(self) -> wgt::ImageDataLayout {
         wgt::ImageDataLayout {
-            offset: data_layout.offset as wgt::BufferAddress,
-            bytes_per_row: data_layout.bytesPerRow,
-            rows_per_image: data_layout.rowsPerImage,
+            offset: self.offset as wgt::BufferAddress,
+            bytes_per_row: self.bytesPerRow,
+            rows_per_image: self.rowsPerImage,
         }
     }
 }
 
-impl From<GPUVertexFormat> for wgt::VertexFormat {
-    fn from(format: GPUVertexFormat) -> Self {
-        match format {
+impl Convert<wgt::VertexFormat> for GPUVertexFormat {
+    fn convert(self) -> wgt::VertexFormat {
+        match self {
             GPUVertexFormat::Uint8x2 => wgt::VertexFormat::Uint8x2,
             GPUVertexFormat::Uint8x4 => wgt::VertexFormat::Uint8x4,
             GPUVertexFormat::Sint8x2 => wgt::VertexFormat::Sint8x2,
@@ -285,34 +286,34 @@ impl From<GPUVertexFormat> for wgt::VertexFormat {
     }
 }
 
-impl From<&GPUPrimitiveState> for wgt::PrimitiveState {
-    fn from(primitive_state: &GPUPrimitiveState) -> Self {
+impl Convert<wgt::PrimitiveState> for &GPUPrimitiveState {
+    fn convert(self) -> wgt::PrimitiveState {
         wgt::PrimitiveState {
-            topology: wgt::PrimitiveTopology::from(&primitive_state.topology),
-            strip_index_format: primitive_state.stripIndexFormat.map(|index_format| {
-                match index_format {
+            topology: self.topology.convert(),
+            strip_index_format: self
+                .stripIndexFormat
+                .map(|index_format| match index_format {
                     GPUIndexFormat::Uint16 => wgt::IndexFormat::Uint16,
                     GPUIndexFormat::Uint32 => wgt::IndexFormat::Uint32,
-                }
-            }),
-            front_face: match primitive_state.frontFace {
+                }),
+            front_face: match self.frontFace {
                 GPUFrontFace::Ccw => wgt::FrontFace::Ccw,
                 GPUFrontFace::Cw => wgt::FrontFace::Cw,
             },
-            cull_mode: match primitive_state.cullMode {
+            cull_mode: match self.cullMode {
                 GPUCullMode::None => None,
                 GPUCullMode::Front => Some(wgt::Face::Front),
                 GPUCullMode::Back => Some(wgt::Face::Back),
             },
-            unclipped_depth: primitive_state.clampDepth,
+            unclipped_depth: self.clampDepth,
             ..Default::default()
         }
     }
 }
 
-impl From<&GPUPrimitiveTopology> for wgt::PrimitiveTopology {
-    fn from(primitive_topology: &GPUPrimitiveTopology) -> Self {
-        match primitive_topology {
+impl Convert<wgt::PrimitiveTopology> for &GPUPrimitiveTopology {
+    fn convert(self) -> wgt::PrimitiveTopology {
+        match self {
             GPUPrimitiveTopology::Point_list => wgt::PrimitiveTopology::PointList,
             GPUPrimitiveTopology::Line_list => wgt::PrimitiveTopology::LineList,
             GPUPrimitiveTopology::Line_strip => wgt::PrimitiveTopology::LineStrip,
@@ -322,9 +323,9 @@ impl From<&GPUPrimitiveTopology> for wgt::PrimitiveTopology {
     }
 }
 
-impl From<GPUAddressMode> for wgt::AddressMode {
-    fn from(address_mode: GPUAddressMode) -> Self {
-        match address_mode {
+impl Convert<wgt::AddressMode> for GPUAddressMode {
+    fn convert(self) -> wgt::AddressMode {
+        match self {
             GPUAddressMode::Clamp_to_edge => wgt::AddressMode::ClampToEdge,
             GPUAddressMode::Repeat => wgt::AddressMode::Repeat,
             GPUAddressMode::Mirror_repeat => wgt::AddressMode::MirrorRepeat,
@@ -332,18 +333,18 @@ impl From<GPUAddressMode> for wgt::AddressMode {
     }
 }
 
-impl From<GPUFilterMode> for wgt::FilterMode {
-    fn from(filter_mode: GPUFilterMode) -> Self {
-        match filter_mode {
+impl Convert<wgt::FilterMode> for GPUFilterMode {
+    fn convert(self) -> wgt::FilterMode {
+        match self {
             GPUFilterMode::Nearest => wgt::FilterMode::Nearest,
             GPUFilterMode::Linear => wgt::FilterMode::Linear,
         }
     }
 }
 
-impl From<GPUTextureViewDimension> for wgt::TextureViewDimension {
-    fn from(view_dimension: GPUTextureViewDimension) -> Self {
-        match view_dimension {
+impl Convert<wgt::TextureViewDimension> for GPUTextureViewDimension {
+    fn convert(self) -> wgt::TextureViewDimension {
+        match self {
             GPUTextureViewDimension::_1d => wgt::TextureViewDimension::D1,
             GPUTextureViewDimension::_2d => wgt::TextureViewDimension::D2,
             GPUTextureViewDimension::_2d_array => wgt::TextureViewDimension::D2Array,
@@ -354,9 +355,9 @@ impl From<GPUTextureViewDimension> for wgt::TextureViewDimension {
     }
 }
 
-impl From<GPUCompareFunction> for wgt::CompareFunction {
-    fn from(compare: GPUCompareFunction) -> Self {
-        match compare {
+impl Convert<wgt::CompareFunction> for GPUCompareFunction {
+    fn convert(self) -> wgt::CompareFunction {
+        match self {
             GPUCompareFunction::Never => wgt::CompareFunction::Never,
             GPUCompareFunction::Less => wgt::CompareFunction::Less,
             GPUCompareFunction::Equal => wgt::CompareFunction::Equal,
@@ -369,9 +370,9 @@ impl From<GPUCompareFunction> for wgt::CompareFunction {
     }
 }
 
-impl From<&GPUBlendFactor> for wgt::BlendFactor {
-    fn from(factor: &GPUBlendFactor) -> Self {
-        match factor {
+impl Convert<wgt::BlendFactor> for &GPUBlendFactor {
+    fn convert(self) -> wgt::BlendFactor {
+        match self {
             GPUBlendFactor::Zero => wgt::BlendFactor::Zero,
             GPUBlendFactor::One => wgt::BlendFactor::One,
             GPUBlendFactor::Src => wgt::BlendFactor::Src,
@@ -389,12 +390,12 @@ impl From<&GPUBlendFactor> for wgt::BlendFactor {
     }
 }
 
-impl From<&GPUBlendComponent> for wgt::BlendComponent {
-    fn from(blend_component: &GPUBlendComponent) -> Self {
+impl Convert<wgt::BlendComponent> for &GPUBlendComponent {
+    fn convert(self) -> wgt::BlendComponent {
         wgt::BlendComponent {
-            src_factor: wgt::BlendFactor::from(&blend_component.srcFactor),
-            dst_factor: wgt::BlendFactor::from(&blend_component.dstFactor),
-            operation: match blend_component.operation {
+            src_factor: self.srcFactor.convert(),
+            dst_factor: self.dstFactor.convert(),
+            operation: match self.operation {
                 GPUBlendOperation::Add => wgt::BlendOperation::Add,
                 GPUBlendOperation::Subtract => wgt::BlendOperation::Subtract,
                 GPUBlendOperation::Reverse_subtract => wgt::BlendOperation::ReverseSubtract,
@@ -421,9 +422,9 @@ pub fn convert_store_op(op: Option<GPUStoreOp>) -> wgpu_com::StoreOp {
     }
 }
 
-impl From<GPUStencilOperation> for wgt::StencilOperation {
-    fn from(operation: GPUStencilOperation) -> Self {
-        match operation {
+impl Convert<wgt::StencilOperation> for GPUStencilOperation {
+    fn convert(self) -> wgt::StencilOperation {
+        match self {
             GPUStencilOperation::Keep => wgt::StencilOperation::Keep,
             GPUStencilOperation::Zero => wgt::StencilOperation::Zero,
             GPUStencilOperation::Replace => wgt::StencilOperation::Replace,
@@ -436,20 +437,20 @@ impl From<GPUStencilOperation> for wgt::StencilOperation {
     }
 }
 
-impl From<&GPUImageCopyBuffer> for wgpu_com::ImageCopyBuffer {
-    fn from(ic_buffer: &GPUImageCopyBuffer) -> Self {
+impl Convert<wgpu_com::ImageCopyBuffer> for &GPUImageCopyBuffer {
+    fn convert(self) -> wgpu_com::ImageCopyBuffer {
         wgpu_com::ImageCopyBuffer {
-            buffer: ic_buffer.buffer.id().0,
-            layout: wgt::ImageDataLayout::from(&ic_buffer.parent),
+            buffer: self.buffer.id().0,
+            layout: self.parent.convert(),
         }
     }
 }
 
-impl TryFrom<&GPUOrigin3D> for wgt::Origin3d {
+impl TryConvert<wgt::Origin3d> for &GPUOrigin3D {
     type Error = Error;
 
-    fn try_from(origin: &GPUOrigin3D) -> Result<Self, Self::Error> {
-        match origin {
+    fn try_convert(self) -> Result<wgt::Origin3d, Self::Error> {
+        match self {
             GPUOrigin3D::RangeEnforcedUnsignedLongSequence(v) => {
                 // https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-gpuorigin3d-shape
                 if v.len() > 3 {
@@ -473,20 +474,20 @@ impl TryFrom<&GPUOrigin3D> for wgt::Origin3d {
     }
 }
 
-impl TryFrom<&GPUImageCopyTexture> for wgpu_com::ImageCopyTexture {
+impl TryConvert<wgpu_com::ImageCopyTexture> for &GPUImageCopyTexture {
     type Error = Error;
 
-    fn try_from(ic_texture: &GPUImageCopyTexture) -> Result<Self, Self::Error> {
+    fn try_convert(self) -> Result<wgpu_com::ImageCopyTexture, Self::Error> {
         Ok(wgpu_com::ImageCopyTexture {
-            texture: ic_texture.texture.id().0,
-            mip_level: ic_texture.mipLevel,
-            origin: ic_texture
+            texture: self.texture.id().0,
+            mip_level: self.mipLevel,
+            origin: self
                 .origin
                 .as_ref()
-                .map(wgt::Origin3d::try_from)
+                .map(TryConvert::<wgt::Origin3d>::try_convert)
                 .transpose()?
                 .unwrap_or_default(),
-            aspect: match ic_texture.aspect {
+            aspect: match self.aspect {
                 GPUTextureAspect::All => wgt::TextureAspect::All,
                 GPUTextureAspect::Stencil_only => wgt::TextureAspect::StencilOnly,
                 GPUTextureAspect::Depth_only => wgt::TextureAspect::DepthOnly,
@@ -495,12 +496,12 @@ impl TryFrom<&GPUImageCopyTexture> for wgpu_com::ImageCopyTexture {
     }
 }
 
-impl<'a> From<&GPUObjectDescriptorBase> for Option<Cow<'a, str>> {
-    fn from(val: &GPUObjectDescriptorBase) -> Self {
-        if val.label.is_empty() {
+impl<'a> Convert<Option<Cow<'a, str>>> for &GPUObjectDescriptorBase {
+    fn convert(self) -> Option<Cow<'a, str>> {
+        if self.label.is_empty() {
             None
         } else {
-            Some(Cow::Owned(val.label.to_string()))
+            Some(Cow::Owned(self.label.to_string()))
         }
     }
 }
@@ -541,7 +542,7 @@ pub fn convert_bind_group_layout_entry(
                 GPUStorageTextureAccess::Read_write => wgt::StorageTextureAccess::ReadWrite,
             },
             format: device.validate_texture_format_required_features(&storage.format)?,
-            view_dimension: storage.viewDimension.into(),
+            view_dimension: storage.viewDimension.convert(),
         })
     } else if let Some(texture) = &bgle.texture {
         Some(wgt::BindingType::Texture {
@@ -554,7 +555,7 @@ pub fn convert_bind_group_layout_entry(
                 GPUTextureSampleType::Sint => wgt::TextureSampleType::Sint,
                 GPUTextureSampleType::Uint => wgt::TextureSampleType::Uint,
             },
-            view_dimension: texture.viewDimension.into(),
+            view_dimension: texture.viewDimension.convert(),
             multisampled: texture.multisampled,
         })
     } else {
@@ -584,13 +585,13 @@ pub fn convert_texture_descriptor(
     descriptor: &GPUTextureDescriptor,
     device: &GPUDevice,
 ) -> Fallible<(TextureDescriptor<'static>, wgt::Extent3d)> {
-    let size = (&descriptor.size).try_into()?;
+    let size = (&descriptor.size).try_convert()?;
     let desc = TextureDescriptor {
-        label: (&descriptor.parent).into(),
+        label: (&descriptor.parent).convert(),
         size,
         mip_level_count: descriptor.mipLevelCount,
         sample_count: descriptor.sampleCount,
-        dimension: descriptor.dimension.into(),
+        dimension: descriptor.dimension.convert(),
         format: device.validate_texture_format_required_features(&descriptor.format)?,
         usage: wgt::TextureUsages::from_bits_retain(descriptor.usage),
         view_formats: descriptor
@@ -602,11 +603,11 @@ pub fn convert_texture_descriptor(
     Ok((desc, size))
 }
 
-impl TryFrom<&GPUColor> for wgt::Color {
+impl TryConvert<wgt::Color> for &GPUColor {
     type Error = Error;
 
-    fn try_from(color: &GPUColor) -> Result<Self, Self::Error> {
-        match color {
+    fn try_convert(self) -> Result<wgt::Color, Self::Error> {
+        match self {
             GPUColor::DoubleSequence(s) => {
                 // https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-gpucolor-shape
                 if s.len() != 4 {
@@ -630,17 +631,16 @@ impl TryFrom<&GPUColor> for wgt::Color {
     }
 }
 
-impl<'a> From<&GPUProgrammableStage> for ProgrammableStageDescriptor<'a> {
-    fn from(stage: &GPUProgrammableStage) -> Self {
-        Self {
-            module: stage.module.id().0,
-            entry_point: stage
+impl<'a> Convert<ProgrammableStageDescriptor<'a>> for &GPUProgrammableStage {
+    fn convert(self) -> ProgrammableStageDescriptor<'a> {
+        ProgrammableStageDescriptor {
+            module: self.module.id().0,
+            entry_point: self
                 .entryPoint
                 .as_ref()
                 .map(|ep| Cow::Owned(ep.to_string())),
             constants: Cow::Owned(
-                stage
-                    .constants
+                self.constants
                     .as_ref()
                     .map(|records| records.iter().map(|(k, v)| (k.0.clone(), **v)).collect())
                     .unwrap_or_default(),
@@ -650,11 +650,11 @@ impl<'a> From<&GPUProgrammableStage> for ProgrammableStageDescriptor<'a> {
     }
 }
 
-impl From<&GPUBindGroupEntry> for BindGroupEntry<'_> {
-    fn from(entry: &GPUBindGroupEntry) -> Self {
-        Self {
-            binding: entry.binding,
-            resource: match entry.resource {
+impl<'a> Convert<BindGroupEntry<'a>> for &GPUBindGroupEntry {
+    fn convert(self) -> BindGroupEntry<'a> {
+        BindGroupEntry {
+            binding: self.binding,
+            resource: match self.resource {
                 GPUBindingResource::GPUSampler(ref s) => BindingResource::Sampler(s.id().0),
                 GPUBindingResource::GPUTextureView(ref t) => BindingResource::TextureView(t.id().0),
                 GPUBindingResource::GPUBufferBinding(ref b) => {
@@ -669,9 +669,9 @@ impl From<&GPUBindGroupEntry> for BindGroupEntry<'_> {
     }
 }
 
-impl From<GPUTextureDimension> for wgt::TextureDimension {
-    fn from(dimension: GPUTextureDimension) -> Self {
-        match dimension {
+impl Convert<wgt::TextureDimension> for GPUTextureDimension {
+    fn convert(self) -> wgt::TextureDimension {
+        match self {
             GPUTextureDimension::_1d => wgt::TextureDimension::D1,
             GPUTextureDimension::_2d => wgt::TextureDimension::D2,
             GPUTextureDimension::_3d => wgt::TextureDimension::D3,
diff --git a/components/script/dom/webgpu/gpudevice.rs b/components/script/dom/webgpu/gpudevice.rs
index 62b47bc7661dc..589395b0a2bdc 100644
--- a/components/script/dom/webgpu/gpudevice.rs
+++ b/components/script/dom/webgpu/gpudevice.rs
@@ -23,6 +23,7 @@ use super::gpu::AsyncWGPUListener;
 use super::gpudevicelostinfo::GPUDeviceLostInfo;
 use super::gpupipelineerror::GPUPipelineError;
 use super::gpusupportedlimits::GPUSupportedLimits;
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
 use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
@@ -215,7 +216,7 @@ impl GPUDevice {
         &self,
         format: &GPUTextureFormat,
     ) -> Fallible<TextureFormat> {
-        let texture_format: TextureFormat = (*format).into();
+        let texture_format: TextureFormat = (*format).convert();
         if self
             .features
             .wgpu_features()
@@ -258,11 +259,11 @@ impl GPUDevice {
         let pipeline_layout = self.get_pipeline_layout_data(&descriptor.parent.layout);
 
         let desc = wgpu_pipe::RenderPipelineDescriptor {
-            label: (&descriptor.parent.parent).into(),
+            label: (&descriptor.parent.parent).convert(),
             layout: pipeline_layout.explicit(),
             cache: None,
             vertex: wgpu_pipe::VertexState {
-                stage: (&descriptor.vertex.parent).into(),
+                stage: (&descriptor.vertex.parent).convert(),
                 buffers: Cow::Owned(
                     descriptor
                         .vertex
@@ -279,7 +280,7 @@ impl GPUDevice {
                                     .attributes
                                     .iter()
                                     .map(|att| wgt::VertexAttribute {
-                                        format: att.format.into(),
+                                        format: att.format.convert(),
                                         offset: att.offset,
                                         shader_location: att.shaderLocation,
                                     })
@@ -294,7 +295,7 @@ impl GPUDevice {
                 .as_ref()
                 .map(|stage| -> Fallible<wgpu_pipe::FragmentState> {
                     Ok(wgpu_pipe::FragmentState {
-                        stage: (&stage.parent).into(),
+                        stage: (&stage.parent).convert(),
                         targets: Cow::Owned(
                             stage
                                 .targets
@@ -309,8 +310,8 @@ impl GPUDevice {
                                                 ),
                                                 blend: state.blend.as_ref().map(|blend| {
                                                     wgt::BlendState {
-                                                        color: (&blend.color).into(),
-                                                        alpha: (&blend.alpha).into(),
+                                                        color: (&blend.color).convert(),
+                                                        alpha: (&blend.alpha).convert(),
                                                     }
                                                 }),
                                             })
@@ -321,7 +322,7 @@ impl GPUDevice {
                     })
                 })
                 .transpose()?,
-            primitive: (&descriptor.primitive).into(),
+            primitive: (&descriptor.primitive).convert(),
             depth_stencil: descriptor
                 .depthStencil
                 .as_ref()
@@ -330,20 +331,20 @@ impl GPUDevice {
                         .map(|format| wgt::DepthStencilState {
                             format,
                             depth_write_enabled: dss_desc.depthWriteEnabled,
-                            depth_compare: dss_desc.depthCompare.into(),
+                            depth_compare: dss_desc.depthCompare.convert(),
                             stencil: wgt::StencilState {
                                 front: wgt::StencilFaceState {
-                                    compare: dss_desc.stencilFront.compare.into(),
+                                    compare: dss_desc.stencilFront.compare.convert(),
 
-                                    fail_op: dss_desc.stencilFront.failOp.into(),
-                                    depth_fail_op: dss_desc.stencilFront.depthFailOp.into(),
-                                    pass_op: dss_desc.stencilFront.passOp.into(),
+                                    fail_op: dss_desc.stencilFront.failOp.convert(),
+                                    depth_fail_op: dss_desc.stencilFront.depthFailOp.convert(),
+                                    pass_op: dss_desc.stencilFront.passOp.convert(),
                                 },
                                 back: wgt::StencilFaceState {
-                                    compare: dss_desc.stencilBack.compare.into(),
-                                    fail_op: dss_desc.stencilBack.failOp.into(),
-                                    depth_fail_op: dss_desc.stencilBack.depthFailOp.into(),
-                                    pass_op: dss_desc.stencilBack.passOp.into(),
+                                    compare: dss_desc.stencilBack.compare.convert(),
+                                    fail_op: dss_desc.stencilBack.failOp.convert(),
+                                    depth_fail_op: dss_desc.stencilBack.depthFailOp.convert(),
+                                    pass_op: dss_desc.stencilBack.passOp.convert(),
                                 },
                                 read_mask: dss_desc.stencilReadMask,
                                 write_mask: dss_desc.stencilWriteMask,
diff --git a/components/script/dom/webgpu/gpuerror.rs b/components/script/dom/webgpu/gpuerror.rs
index a4de08cc18b79..bc349ed437789 100644
--- a/components/script/dom/webgpu/gpuerror.rs
+++ b/components/script/dom/webgpu/gpuerror.rs
@@ -6,6 +6,7 @@ use dom_struct::dom_struct;
 use js::rust::HandleObject;
 use webgpu::{Error, ErrorFilter};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{GPUErrorFilter, GPUErrorMethods};
 use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
 use crate::dom::bindings::root::DomRoot;
@@ -79,9 +80,9 @@ impl GPUErrorMethods<crate::DomTypeHolder> for GPUError {
     }
 }
 
-impl From<ErrorFilter> for GPUErrorFilter {
-    fn from(filter: ErrorFilter) -> Self {
-        match filter {
+impl Convert<GPUErrorFilter> for ErrorFilter {
+    fn convert(self) -> GPUErrorFilter {
+        match self {
             ErrorFilter::Validation => GPUErrorFilter::Validation,
             ErrorFilter::OutOfMemory => GPUErrorFilter::Out_of_memory,
             ErrorFilter::Internal => GPUErrorFilter::Internal,
diff --git a/components/script/dom/webgpu/gpupipelinelayout.rs b/components/script/dom/webgpu/gpupipelinelayout.rs
index 5a5768388c440..10bbc53f69095 100644
--- a/components/script/dom/webgpu/gpupipelinelayout.rs
+++ b/components/script/dom/webgpu/gpupipelinelayout.rs
@@ -8,6 +8,7 @@ use dom_struct::dom_struct;
 use webgpu::wgc::binding_model::PipelineLayoutDescriptor;
 use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPUPipelineLayout, WebGPURequest};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUPipelineLayoutDescriptor, GPUPipelineLayoutMethods,
@@ -87,7 +88,7 @@ impl GPUPipelineLayout {
             .collect::<Vec<_>>();
 
         let desc = PipelineLayoutDescriptor {
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             bind_group_layouts: Cow::Owned(bgls.iter().map(|l| l.0).collect::<Vec<_>>()),
             push_constant_ranges: Cow::Owned(vec![]),
         };
diff --git a/components/script/dom/webgpu/gpuqueue.rs b/components/script/dom/webgpu/gpuqueue.rs
index 9eea0bffa690d..72e74ba66d3d4 100644
--- a/components/script/dom/webgpu/gpuqueue.rs
+++ b/components/script/dom/webgpu/gpuqueue.rs
@@ -9,6 +9,7 @@ use ipc_channel::ipc::IpcSharedMemory;
 use webgpu::{wgt, WebGPU, WebGPUQueue, WebGPURequest, WebGPUResponse};
 
 use super::gpu::{response_async, AsyncWGPUListener};
+use crate::conversions::{Convert, TryConvert};
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUExtent3D, GPUImageCopyTexture, GPUImageDataLayout, GPUQueueMethods, GPUSize64,
@@ -162,9 +163,9 @@ impl GPUQueueMethods<crate::DomTypeHolder> for GPUQueue {
             return Err(Error::Operation);
         }
 
-        let texture_cv = destination.try_into()?;
-        let texture_layout = data_layout.into();
-        let write_size = (&size).try_into()?;
+        let texture_cv = destination.try_convert()?;
+        let texture_layout = data_layout.convert();
+        let write_size = (&size).try_convert()?;
         let final_data = IpcSharedMemory::from_bytes(&bytes);
 
         if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture {
diff --git a/components/script/dom/webgpu/gpurenderbundleencoder.rs b/components/script/dom/webgpu/gpurenderbundleencoder.rs
index 2a76bfe03361e..11483bc3eacaa 100644
--- a/components/script/dom/webgpu/gpurenderbundleencoder.rs
+++ b/components/script/dom/webgpu/gpurenderbundleencoder.rs
@@ -10,6 +10,7 @@ use webgpu::wgc::command::{
 };
 use webgpu::{wgt, WebGPU, WebGPURenderBundle, WebGPURequest};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUIndexFormat, GPURenderBundleDescriptor, GPURenderBundleEncoderDescriptor,
@@ -81,7 +82,7 @@ impl GPURenderBundleEncoder {
         descriptor: &GPURenderBundleEncoderDescriptor,
     ) -> Fallible<DomRoot<GPURenderBundleEncoder>> {
         let desc = RenderBundleEncoderDescriptor {
-            label: (&descriptor.parent.parent).into(),
+            label: (&descriptor.parent.parent).convert(),
             color_formats: Cow::Owned(
                 descriptor
                     .parent
@@ -252,7 +253,7 @@ impl GPURenderBundleEncoderMethods<crate::DomTypeHolder> for GPURenderBundleEnco
     /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderbundleencoder-finish>
     fn Finish(&self, descriptor: &GPURenderBundleDescriptor) -> DomRoot<GPURenderBundle> {
         let desc = wgt::RenderBundleDescriptor {
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
         };
         let encoder = self.render_bundle_encoder.borrow_mut().take().unwrap();
         let render_bundle_id = self.global().wgpu_id_hub().create_render_bundle_id();
diff --git a/components/script/dom/webgpu/gpurenderpassencoder.rs b/components/script/dom/webgpu/gpurenderpassencoder.rs
index 765e220f32e97..e52cb4dec4559 100644
--- a/components/script/dom/webgpu/gpurenderpassencoder.rs
+++ b/components/script/dom/webgpu/gpurenderpassencoder.rs
@@ -5,6 +5,7 @@
 use dom_struct::dom_struct;
 use webgpu::{wgt, RenderCommand, WebGPU, WebGPURenderPass, WebGPURequest};
 
+use crate::conversions::TryConvert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUColor, GPUIndexFormat, GPURenderPassEncoderMethods,
@@ -130,7 +131,7 @@ impl GPURenderPassEncoderMethods<crate::DomTypeHolder> for GPURenderPassEncoder
 
     /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setblendcolor>
     fn SetBlendConstant(&self, color: GPUColor) -> Fallible<()> {
-        self.send_render_command(RenderCommand::SetBlendConstant((&color).try_into()?));
+        self.send_render_command(RenderCommand::SetBlendConstant((&color).try_convert()?));
         Ok(())
     }
 
diff --git a/components/script/dom/webgpu/gpusampler.rs b/components/script/dom/webgpu/gpusampler.rs
index 25fa90a2410b9..98ec5a873ff69 100644
--- a/components/script/dom/webgpu/gpusampler.rs
+++ b/components/script/dom/webgpu/gpusampler.rs
@@ -6,6 +6,7 @@ use dom_struct::dom_struct;
 use webgpu::wgc::resource::SamplerDescriptor;
 use webgpu::{WebGPU, WebGPUDevice, WebGPURequest, WebGPUSampler};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUSamplerDescriptor, GPUSamplerMethods,
@@ -79,18 +80,18 @@ impl GPUSampler {
         let sampler_id = device.global().wgpu_id_hub().create_sampler_id();
         let compare_enable = descriptor.compare.is_some();
         let desc = SamplerDescriptor {
-            label: (&descriptor.parent).into(),
+            label: (&descriptor.parent).convert(),
             address_modes: [
-                descriptor.addressModeU.into(),
-                descriptor.addressModeV.into(),
-                descriptor.addressModeW.into(),
+                descriptor.addressModeU.convert(),
+                descriptor.addressModeV.convert(),
+                descriptor.addressModeW.convert(),
             ],
-            mag_filter: descriptor.magFilter.into(),
-            min_filter: descriptor.minFilter.into(),
-            mipmap_filter: descriptor.mipmapFilter.into(),
+            mag_filter: descriptor.magFilter.convert(),
+            min_filter: descriptor.minFilter.convert(),
+            mipmap_filter: descriptor.mipmapFilter.convert(),
             lod_min_clamp: *descriptor.lodMinClamp,
             lod_max_clamp: *descriptor.lodMaxClamp,
-            compare: descriptor.compare.map(Into::into),
+            compare: descriptor.compare.map(Convert::convert),
             anisotropy_clamp: 1,
             border_color: None,
         };
diff --git a/components/script/dom/webgpu/gputexture.rs b/components/script/dom/webgpu/gputexture.rs
index 02cb4c94f7452..9a136165dffa2 100644
--- a/components/script/dom/webgpu/gputexture.rs
+++ b/components/script/dom/webgpu/gputexture.rs
@@ -9,6 +9,7 @@ use webgpu::wgc::resource;
 use webgpu::{wgt, WebGPU, WebGPURequest, WebGPUTexture, WebGPUTextureView};
 
 use super::gpuconvert::convert_texture_descriptor;
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
     GPUTextureAspect, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat,
@@ -180,12 +181,12 @@ impl GPUTextureMethods<crate::DomTypeHolder> for GPUTexture {
             !matches!(descriptor.arrayLayerCount, Some(0))
         {
             Some(resource::TextureViewDescriptor {
-                label: (&descriptor.parent).into(),
+                label: (&descriptor.parent).convert(),
                 format: descriptor
                     .format
                     .map(|f| self.device.validate_texture_format_required_features(&f))
                     .transpose()?,
-                dimension: descriptor.dimension.map(|dimension| dimension.into()),
+                dimension: descriptor.dimension.map(|dimension| dimension.convert()),
                 range: wgt::ImageSubresourceRange {
                     aspect: match descriptor.aspect {
                         GPUTextureAspect::All => wgt::TextureAspect::All,
diff --git a/components/script/dom/webxr/fakexrdevice.rs b/components/script/dom/webxr/fakexrdevice.rs
index 7024aa9dc65f6..036e3022668d8 100644
--- a/components/script/dom/webxr/fakexrdevice.rs
+++ b/components/script/dom/webxr/fakexrdevice.rs
@@ -15,6 +15,7 @@ use webxr_api::{
     MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility,
 };
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
 use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{
     FakeXRBoundsPoint, FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit,
@@ -147,7 +148,7 @@ pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
         .hitTestRegions
         .iter()
         .map(|region| {
-            let ty = region.type_.into();
+            let ty = region.type_.convert();
             let faces = region
                 .faces
                 .iter()
@@ -172,9 +173,9 @@ pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
     Ok(MockWorld { regions })
 }
 
-impl From<FakeXRRegionType> for EntityType {
-    fn from(x: FakeXRRegionType) -> Self {
-        match x {
+impl Convert<EntityType> for FakeXRRegionType {
+    fn convert(self) -> EntityType {
+        match self {
             FakeXRRegionType::Point => EntityType::Point,
             FakeXRRegionType::Plane => EntityType::Plane,
             FakeXRRegionType::Mesh => EntityType::Mesh,
@@ -255,8 +256,8 @@ impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
         let id = self.next_input_id.get();
         self.next_input_id.set(InputId(id.0 + 1));
 
-        let handedness = init.handedness.into();
-        let target_ray_mode = init.targetRayMode.into();
+        let handedness = init.handedness.convert();
+        let target_ray_mode = init.targetRayMode.convert();
 
         let pointer_origin = Some(get_origin(&init.pointerOrigin)?);
 
@@ -346,9 +347,9 @@ impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
     }
 }
 
-impl From<XRHandedness> for Handedness {
-    fn from(h: XRHandedness) -> Self {
-        match h {
+impl Convert<Handedness> for XRHandedness {
+    fn convert(self) -> Handedness {
+        match self {
             XRHandedness::None => Handedness::None,
             XRHandedness::Left => Handedness::Left,
             XRHandedness::Right => Handedness::Right,
@@ -356,9 +357,9 @@ impl From<XRHandedness> for Handedness {
     }
 }
 
-impl From<XRTargetRayMode> for TargetRayMode {
-    fn from(t: XRTargetRayMode) -> Self {
-        match t {
+impl Convert<TargetRayMode> for XRTargetRayMode {
+    fn convert(self) -> TargetRayMode {
+        match self {
             XRTargetRayMode::Gaze => TargetRayMode::Gaze,
             XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
             XRTargetRayMode::Screen => TargetRayMode::Screen,
diff --git a/components/script/dom/webxr/fakexrinputcontroller.rs b/components/script/dom/webxr/fakexrinputcontroller.rs
index a9bf8038f5eda..f1ab931aa74b1 100644
--- a/components/script/dom/webxr/fakexrinputcontroller.rs
+++ b/components/script/dom/webxr/fakexrinputcontroller.rs
@@ -9,6 +9,7 @@ use webxr_api::{
     SelectKind, TargetRayMode,
 };
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::FakeXRRigidTransformInit;
 use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::{
     FakeXRButtonStateInit, FakeXRButtonType, FakeXRInputControllerMethods,
@@ -165,9 +166,9 @@ impl FakeXRInputControllerMethods<crate::DomTypeHolder> for FakeXRInputControlle
     }
 }
 
-impl From<FakeXRButtonType> for MockButtonType {
-    fn from(b: FakeXRButtonType) -> Self {
-        match b {
+impl Convert<MockButtonType> for FakeXRButtonType {
+    fn convert(self) -> MockButtonType {
+        match self {
             FakeXRButtonType::Grip => MockButtonType::Grip,
             FakeXRButtonType::Touchpad => MockButtonType::Touchpad,
             FakeXRButtonType::Thumbstick => MockButtonType::Thumbstick,
@@ -182,7 +183,7 @@ pub fn init_to_mock_buttons(buttons: &[FakeXRButtonStateInit]) -> Vec<MockButton
     let supported: Vec<MockButton> = buttons
         .iter()
         .map(|b| MockButton {
-            button_type: b.buttonType.into(),
+            button_type: b.buttonType.convert(),
             pressed: b.pressed,
             touched: b.touched,
             pressed_value: *b.pressedValue,
diff --git a/components/script/dom/webxr/xrsession.rs b/components/script/dom/webxr/xrsession.rs
index 920c94aaada53..b2ed2c8e08fa6 100644
--- a/components/script/dom/webxr/xrsession.rs
+++ b/components/script/dom/webxr/xrsession.rs
@@ -24,6 +24,7 @@ use webxr_api::{
     SelectEvent, SelectKind, Session, SessionId, View, Viewer, Visibility,
 };
 
+use crate::conversions::Convert;
 use crate::dom::bindings::trace::HashMapTracedValues;
 use crate::dom::bindings::buffer_source::create_buffer_source;
 use crate::dom::bindings::callback::ExceptionHandling;
@@ -124,7 +125,7 @@ impl XRSession {
     ) -> XRSession {
         XRSession {
             eventtarget: EventTarget::new_inherited(),
-            blend_mode: session.environment_blend_mode().into(),
+            blend_mode: session.environment_blend_mode().convert(),
             mode,
             visibility_state: Cell::new(XRVisibilityState::Visible),
             viewer_space: Default::default(),
@@ -1100,9 +1101,9 @@ pub fn cast_transform<T, U, V, W>(
     unsafe { mem::transmute(transform) }
 }
 
-impl From<EnvironmentBlendMode> for XREnvironmentBlendMode {
-    fn from(x: EnvironmentBlendMode) -> Self {
-        match x {
+impl Convert<XREnvironmentBlendMode> for EnvironmentBlendMode {
+    fn convert(self) -> XREnvironmentBlendMode {
+        match self {
             EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque,
             EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend,
             EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive,
diff --git a/components/script/dom/webxr/xrsystem.rs b/components/script/dom/webxr/xrsystem.rs
index 21705357e0b0b..cf5e37b78ecb1 100644
--- a/components/script/dom/webxr/xrsystem.rs
+++ b/components/script/dom/webxr/xrsystem.rs
@@ -13,6 +13,7 @@ use profile_traits::ipc;
 use servo_config::pref;
 use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
 
+use crate::conversions::Convert;
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
     XRSessionInit, XRSessionMode, XRSystemMethods,
@@ -100,9 +101,9 @@ impl XRSystem {
     }
 }
 
-impl From<XRSessionMode> for SessionMode {
-    fn from(mode: XRSessionMode) -> SessionMode {
-        match mode {
+impl Convert<SessionMode> for XRSessionMode {
+    fn convert(self) -> SessionMode {
+        match self {
             XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR,
             XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR,
             XRSessionMode::Inline => SessionMode::Inline,
@@ -148,7 +149,7 @@ impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
             }),
         );
         if let Some(mut r) = window.webxr_registry() {
-            r.supports_session(mode.into(), sender);
+            r.supports_session(mode.convert(), sender);
         }
 
         promise
@@ -266,7 +267,7 @@ impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
             }),
         );
         if let Some(mut r) = window.webxr_registry() {
-            r.request_session(mode.into(), init, sender, frame_sender);
+            r.request_session(mode.convert(), init, sender, frame_sender);
         }
         promise
     }
diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs
index 2a9058ca9a18a..6c9cbe5bea1a5 100644
--- a/components/script/dom/worklet.rs
+++ b/components/script/dom/worklet.rs
@@ -30,6 +30,7 @@ use style::thread_state::{self, ThreadState};
 use swapper::{swapper, Swapper};
 use uuid::Uuid;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
 use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
 use crate::dom::bindings::codegen::Bindings::WorkletBinding::{WorkletMethods, WorkletOptions};
@@ -655,7 +656,7 @@ impl WorkletThread {
         )
         .destination(Destination::Script)
         .mode(RequestMode::CorsMode)
-        .credentials_mode(credentials.into())
+        .credentials_mode(credentials.convert())
         .origin(origin);
 
         let script = load_whole_resource(
diff --git a/components/script/lib.rs b/components/script/lib.rs
index 135c98ec3fe70..80a56236a2a61 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -34,6 +34,8 @@ mod body;
 #[warn(deprecated)]
 pub mod clipboard_provider;
 #[warn(deprecated)]
+pub mod conversions;
+#[warn(deprecated)]
 mod devtools;
 #[warn(deprecated)]
 pub mod document_loader;
diff --git a/components/script/security_manager.rs b/components/script/security_manager.rs
index c0c1497029f7b..22d99c07e7226 100644
--- a/components/script/security_manager.rs
+++ b/components/script/security_manager.rs
@@ -8,6 +8,7 @@ use serde::Serialize;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
 use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{
     SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit,
@@ -111,7 +112,7 @@ impl CSPViolationReporter {
             Atom::from("securitypolicyviolation"),
             EventBubbles::Bubbles,
             EventCancelable::Cancelable,
-            &report.into(),
+            &report.convert(),
             can_gc,
         );
 
@@ -149,21 +150,21 @@ impl TaskOnce for CSPViolationReporter {
     }
 }
 
-impl From<SecurityPolicyViolationReport> for SecurityPolicyViolationEventInit {
-    fn from(value: SecurityPolicyViolationReport) -> Self {
+impl Convert<SecurityPolicyViolationEventInit> for SecurityPolicyViolationReport {
+    fn convert(self) -> SecurityPolicyViolationEventInit {
         SecurityPolicyViolationEventInit {
-            sample: value.sample.unwrap_or_default().into(),
-            blockedURI: value.blocked_url.into(),
-            referrer: value.referrer.into(),
-            statusCode: value.status_code,
-            documentURI: value.document_url.into(),
-            sourceFile: value.source_file.into(),
-            violatedDirective: value.violated_directive.into(),
-            effectiveDirective: value.effective_directive.into(),
-            lineNumber: value.line_number,
-            columnNumber: value.column_number,
-            originalPolicy: value.original_policy.into(),
-            disposition: value.disposition,
+            sample: self.sample.unwrap_or_default().into(),
+            blockedURI: self.blocked_url.into(),
+            referrer: self.referrer.into(),
+            statusCode: self.status_code,
+            documentURI: self.document_url.into(),
+            sourceFile: self.source_file.into(),
+            violatedDirective: self.violated_directive.into(),
+            effectiveDirective: self.effective_directive.into(),
+            lineNumber: self.line_number,
+            columnNumber: self.column_number,
+            originalPolicy: self.original_policy.into(),
+            disposition: self.disposition,
             parent: EventInit::empty(),
         }
     }


Test patch:
diff --git a/components/script/dom/testrunner.rs b/components/script/dom/testrunner.rs
index ae62777bab71d..701884f284427 100644
--- a/components/script/dom/testrunner.rs
+++ b/components/script/dom/testrunner.rs
@@ -7,8 +7,9 @@ use dom_struct::dom_struct;
 use ipc_channel::ipc::IpcSender;
 use profile_traits::ipc;
 
+use crate::conversions::Convert;
 use crate::dom::bindings::codegen::Bindings::TestRunnerBinding::TestRunnerMethods;
-use crate::dom::bindings::error::{Error, ErrorResult};
+use crate::dom::bindings::error::ErrorResult;
 use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
 use crate::dom::bindings::root::DomRoot;
 use crate::dom::bindings::str::DOMString;
@@ -46,7 +47,7 @@ impl TestRunnerMethods<crate::DomTypeHolder> for TestRunner {
             .unwrap();
         match receiver.recv().unwrap() {
             Ok(()) => Ok(()),
-            Err(error) => Err(Error::from(error)),
+            Err(error) => Err(error.convert()),
         }
     }
 }