/* 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/. */

use std::rc::Rc;
use std::str::FromStr;

use cssparser::match_ignore_ascii_case;
use dom_struct::dom_struct;
use http::Method as HttpMethod;
use http::header::{HeaderName, HeaderValue};
use http::method::InvalidMethod;
use js::rust::HandleObject;
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
use net_traits::fetch::headers::is_forbidden_method;
use net_traits::request::{
    CacheMode as NetTraitsRequestCache, CredentialsMode as NetTraitsRequestCredentials,
    Destination as NetTraitsRequestDestination, Origin, RedirectMode as NetTraitsRequestRedirect,
    Referrer as NetTraitsRequestReferrer, Request as NetTraitsRequest, RequestBuilder,
    RequestMode as NetTraitsRequestMode, Window,
};
use servo_url::ServoUrl;

use crate::body::{BodyMixin, BodyType, Extractable, consume_body};
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::{
    ReferrerPolicy, RequestCache, RequestCredentials, RequestDestination, RequestInfo, RequestInit,
    RequestMethods, RequestMode, RequestRedirect,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{ByteString, DOMString, USVString};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{Guard, Headers};
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::CanGc;

#[dom_struct]
pub(crate) struct Request {
    reflector_: Reflector,
    #[no_trace]
    request: DomRefCell<NetTraitsRequest>,
    body_stream: MutNullableDom<ReadableStream>,
    headers: MutNullableDom<Headers>,
}

impl Request {
    fn new_inherited(global: &GlobalScope, url: ServoUrl) -> Request {
        Request {
            reflector_: Reflector::new(),
            request: DomRefCell::new(net_request_from_global(global, url)),
            body_stream: MutNullableDom::new(None),
            headers: Default::default(),
        }
    }

    fn new(
        global: &GlobalScope,
        proto: Option<HandleObject>,
        url: ServoUrl,
        can_gc: CanGc,
    ) -> DomRoot<Request> {
        reflect_dom_object_with_proto(
            Box::new(Request::new_inherited(global, url)),
            global,
            proto,
            can_gc,
        )
    }

    fn from_net_request(
        global: &GlobalScope,
        proto: Option<HandleObject>,
        net_request: NetTraitsRequest,
        can_gc: CanGc,
    ) -> DomRoot<Request> {
        let r = Request::new(global, proto, net_request.current_url(), can_gc);
        *r.request.borrow_mut() = net_request;
        r
    }

    fn clone_from(r: &Request, can_gc: CanGc) -> Fallible<DomRoot<Request>> {
        let req = r.request.borrow();
        let url = req.url();
        let headers_guard = r.Headers(can_gc).get_guard();
        let r_clone = Request::new(&r.global(), None, url, can_gc);
        r_clone.request.borrow_mut().pipeline_id = req.pipeline_id;
        {
            let mut borrowed_r_request = r_clone.request.borrow_mut();
            borrowed_r_request.origin = req.origin.clone();
        }
        *r_clone.request.borrow_mut() = req.clone();
        r_clone
            .Headers(can_gc)
            .copy_from_headers(r.Headers(can_gc))?;
        r_clone.Headers(can_gc).set_guard(headers_guard);
        Ok(r_clone)
    }

    pub(crate) fn get_request(&self) -> NetTraitsRequest {
        self.request.borrow().clone()
    }
}

fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequest {
    RequestBuilder::new(global.webview_id(), url, global.get_referrer())
        .origin(global.get_url().origin())
        .pipeline_id(Some(global.pipeline_id()))
        .https_state(global.get_https_state())
        .insecure_requests_policy(global.insecure_requests_policy())
        .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
        .build()
}

// https://fetch.spec.whatwg.org/#concept-method-normalize
fn normalize_method(m: &str) -> Result<HttpMethod, InvalidMethod> {
    match_ignore_ascii_case! { m,
        "delete" => return Ok(HttpMethod::DELETE),
        "get" => return Ok(HttpMethod::GET),
        "head" => return Ok(HttpMethod::HEAD),
        "options" => return Ok(HttpMethod::OPTIONS),
        "post" => return Ok(HttpMethod::POST),
        "put" => return Ok(HttpMethod::PUT),
        _ => (),
    }
    debug!("Method: {:?}", m);
    HttpMethod::from_str(m)
}

// https://fetch.spec.whatwg.org/#concept-method
fn is_method(m: &ByteString) -> bool {
    m.as_str().is_some()
}

// https://fetch.spec.whatwg.org/#cors-safelisted-method
fn is_cors_safelisted_method(m: &HttpMethod) -> bool {
    m == HttpMethod::GET || m == HttpMethod::HEAD || m == HttpMethod::POST
}

// https://url.spec.whatwg.org/#include-credentials
fn includes_credentials(input: &ServoUrl) -> bool {
    !input.username().is_empty() || input.password().is_some()
}

// https://fetch.spec.whatwg.org/#concept-body-disturbed
fn request_is_disturbed(input: &Request) -> bool {
    input.is_disturbed()
}

// https://fetch.spec.whatwg.org/#concept-body-locked
fn request_is_locked(input: &Request) -> bool {
    input.is_locked()
}

impl RequestMethods<crate::DomTypeHolder> for Request {
    // https://fetch.spec.whatwg.org/#dom-request
    fn Constructor(
        global: &GlobalScope,
        proto: Option<HandleObject>,
        can_gc: CanGc,
        mut input: RequestInfo,
        init: RootedTraceableBox<RequestInit>,
    ) -> Fallible<DomRoot<Request>> {
        // Step 1
        let temporary_request: NetTraitsRequest;

        // Step 2
        let mut fallback_mode: Option<NetTraitsRequestMode> = None;

        // Step 3
        let base_url = global.api_base_url();

        // Step 4 TODO: "Let signal be null."

        match input {
            // Step 5
            RequestInfo::USVString(USVString(ref usv_string)) => {
                // Step 5.1
                let parsed_url = base_url.join(usv_string);
                // Step 5.2
                if parsed_url.is_err() {
                    return Err(Error::Type("Url could not be parsed".to_string()));
                }
                // Step 5.3
                let url = parsed_url.unwrap();
                if includes_credentials(&url) {
                    return Err(Error::Type("Url includes credentials".to_string()));
                }
                // Step 5.4
                temporary_request = net_request_from_global(global, url);
                // Step 5.5
                fallback_mode = Some(NetTraitsRequestMode::CorsMode);
            },
            // Step 6
            RequestInfo::Request(ref input_request) => {
                // This looks like Step 38
                // TODO do this in the right place to not mask other errors
                if request_is_disturbed(input_request) || request_is_locked(input_request) {
                    return Err(Error::Type("Input is disturbed or locked".to_string()));
                }
                // Step 6.1
                temporary_request = input_request.request.borrow().clone();
                // Step 6.2 TODO: "Set signal to input's signal."
            },
        }

        // Step 7
        // TODO: `entry settings object` is not implemented yet.
        let origin = base_url.origin();

        // Step 8
        let mut window = Window::Client;

        // Step 9
        // TODO: `environment settings object` is not implemented in Servo yet.

        // Step 10
        if !init.window.handle().is_null_or_undefined() {
            return Err(Error::Type("Window is present and is not null".to_string()));
        }

        // Step 11
        if !init.window.handle().is_undefined() {
            window = Window::NoWindow;
        }

        // Step 12
        let mut request: NetTraitsRequest;
        request = net_request_from_global(global, temporary_request.current_url());
        request.method = temporary_request.method;
        request.headers = temporary_request.headers.clone();
        request.unsafe_request = true;
        request.window = window;
        // TODO: `entry settings object` is not implemented in Servo yet.
        request.origin = Origin::Client;
        request.referrer = temporary_request.referrer;
        request.referrer_policy = temporary_request.referrer_policy;
        request.mode = temporary_request.mode;
        request.credentials_mode = temporary_request.credentials_mode;
        request.cache_mode = temporary_request.cache_mode;
        request.redirect_mode = temporary_request.redirect_mode;
        request.integrity_metadata = temporary_request.integrity_metadata;

        // Step 13
        if init.body.is_some() ||
            init.cache.is_some() ||
            init.credentials.is_some() ||
            init.integrity.is_some() ||
            init.headers.is_some() ||
            init.method.is_some() ||
            init.mode.is_some() ||
            init.redirect.is_some() ||
            init.referrer.is_some() ||
            init.referrerPolicy.is_some() ||
            !init.window.handle().is_undefined()
        {
            // Step 13.1
            if request.mode == NetTraitsRequestMode::Navigate {
                request.mode = NetTraitsRequestMode::SameOrigin;
            }
            // Step 13.2 TODO: "Unset request's reload-navigation flag."
            // Step 13.3 TODO: "Unset request's history-navigation flag."
            // Step 13.4
            request.referrer = global.get_referrer();
            // Step 13.5
            request.referrer_policy = MsgReferrerPolicy::EmptyString;
        }

        // Step 14
        if let Some(init_referrer) = init.referrer.as_ref() {
            // Step 14.1
            let referrer = &init_referrer.0;
            // Step 14.2
            if referrer.is_empty() {
                request.referrer = NetTraitsRequestReferrer::NoReferrer;
            } else {
                // Step 14.3.1
                let parsed_referrer = base_url.join(referrer);
                // Step 14.3.2
                if parsed_referrer.is_err() {
                    return Err(Error::Type("Failed to parse referrer url".to_string()));
                }
                // Step 14.3.3
                if let Ok(parsed_referrer) = parsed_referrer {
                    if (parsed_referrer.cannot_be_a_base() &&
                        parsed_referrer.scheme() == "about" &&
                        parsed_referrer.path() == "client") ||
                        parsed_referrer.origin() != origin
                    {
                        request.referrer = global.get_referrer();
                    } else {
                        // Step 14.3.4
                        request.referrer = NetTraitsRequestReferrer::ReferrerUrl(parsed_referrer);
                    }
                }
            }
        }

        // Step 15
        if let Some(init_referrerpolicy) = init.referrerPolicy.as_ref() {
            let init_referrer_policy = (*init_referrerpolicy).convert();
            request.referrer_policy = init_referrer_policy;
        }

        // Step 16
        let mode = init.mode.as_ref().map(|m| (*m).convert()).or(fallback_mode);

        // Step 17
        if let Some(NetTraitsRequestMode::Navigate) = mode {
            return Err(Error::Type("Request mode is Navigate".to_string()));
        }

        // Step 18
        if let Some(m) = mode {
            request.mode = m;
        }

        // Step 19
        if let Some(init_credentials) = init.credentials.as_ref() {
            let credentials = (*init_credentials).convert();
            request.credentials_mode = credentials;
        }

        // Step 20
        if let Some(init_cache) = init.cache.as_ref() {
            let cache = (*init_cache).convert();
            request.cache_mode = cache;
        }

        // Step 21
        if request.cache_mode == NetTraitsRequestCache::OnlyIfCached &&
            request.mode != NetTraitsRequestMode::SameOrigin
        {
            return Err(Error::Type(
                "Cache is 'only-if-cached' and mode is not 'same-origin'".to_string(),
            ));
        }

        // Step 22
        if let Some(init_redirect) = init.redirect.as_ref() {
            let redirect = (*init_redirect).convert();
            request.redirect_mode = redirect;
        }

        // Step 23
        if let Some(init_integrity) = init.integrity.as_ref() {
            let integrity = init_integrity.clone().to_string();
            request.integrity_metadata = integrity;
        }

        // Step 24 TODO: "If init["keepalive"] exists..."

        // Step 25.1
        if let Some(init_method) = init.method.as_ref() {
            if !is_method(init_method) {
                return Err(Error::Type("Method is not a method".to_string()));
            }
            // Step 25.2
            if is_forbidden_method(init_method) {
                return Err(Error::Type("Method is forbidden".to_string()));
            }
            // Step 25.3
            let method = match init_method.as_str() {
                Some(s) => normalize_method(s)
                    .map_err(|e| Error::Type(format!("Method is not valid: {:?}", e)))?,
                None => return Err(Error::Type("Method is not a valid UTF8".to_string())),
            };
            // Step 25.4
            request.method = method;
        }

        // Step 26 TODO: "If init["signal"] exists..."
        // Step 27 TODO: "If init["priority"] exists..."

        // Step 28
        let r = Request::from_net_request(global, proto, request, can_gc);

        // Step 29 TODO: "Set this's signal to new AbortSignal object..."
        // Step 30 TODO: "If signal is not null..."

        // Step 31
        // "or_init" looks unclear here, but it always enters the block since r
        // hasn't had any other way to initialize its headers
        r.headers
            .or_init(|| Headers::for_request(&r.global(), can_gc));

        // Step 33 - but spec says this should only be when non-empty init?
        let headers_copy = init
            .headers
            .as_ref()
            .map(|possible_header| match possible_header {
                HeadersInit::ByteStringSequenceSequence(init_sequence) => {
                    HeadersInit::ByteStringSequenceSequence(init_sequence.clone())
                },
                HeadersInit::ByteStringByteStringRecord(init_map) => {
                    HeadersInit::ByteStringByteStringRecord(init_map.clone())
                },
            });

        // Step 33.3
        // We cannot empty `r.Headers().header_list` because
        // we would undo the Step 25 above.  One alternative is to set
        // `headers_copy` as a deep copy of `r.Headers()`. However,
        // `r.Headers()` is a `DomRoot<T>`, and therefore it is difficult
        // to obtain a mutable reference to `r.Headers()`. Without the
        // mutable reference, we cannot mutate `r.Headers()` to be the
        // deep copied headers in Step 25.

        // Step 32
        if r.request.borrow().mode == NetTraitsRequestMode::NoCors {
            let borrowed_request = r.request.borrow();
            // Step 32.1
            if !is_cors_safelisted_method(&borrowed_request.method) {
                return Err(Error::Type(
                    "The mode is 'no-cors' but the method is not a cors-safelisted method"
                        .to_string(),
                ));
            }
            // Step 32.2
            r.Headers(can_gc).set_guard(Guard::RequestNoCors);
        }

        // Step 33.5
        match headers_copy {
            None => {
                // This is equivalent to the specification's concept of
                // "associated headers list". If an init headers is not given,
                // but an input with headers is given, set request's
                // headers as the input's Headers.
                if let RequestInfo::Request(ref input_request) = input {
                    r.Headers(can_gc)
                        .copy_from_headers(input_request.Headers(can_gc))?;
                }
            },
            Some(headers_copy) => r.Headers(can_gc).fill(Some(headers_copy))?,
        }

        // Step 33.5 depending on how we got here
        // Copy the headers list onto the headers of net_traits::Request
        r.request.borrow_mut().headers = r.Headers(can_gc).get_headers_list();

        // Step 34
        let mut input_body = if let RequestInfo::Request(ref mut input_request) = input {
            let mut input_request_request = input_request.request.borrow_mut();
            input_request_request.body.take()
        } else {
            None
        };

        // Step 35
        if let Some(init_body_option) = init.body.as_ref() {
            if init_body_option.is_some() || input_body.is_some() {
                let req = r.request.borrow();
                let req_method = &req.method;
                match *req_method {
                    HttpMethod::GET => {
                        return Err(Error::Type(
                            "Init's body is non-null, and request method is GET".to_string(),
                        ));
                    },
                    HttpMethod::HEAD => {
                        return Err(Error::Type(
                            "Init's body is non-null, and request method is HEAD".to_string(),
                        ));
                    },
                    _ => {},
                }
            }
        }

        // Step 36-37
        if let Some(Some(ref init_body)) = init.body {
            // Step 37.1 TODO "If init["keepalive"] exists and is true..."

            // Step 37.2
            let mut extracted_body = init_body.extract(global, can_gc)?;

            // Step 37.3
            if let Some(contents) = extracted_body.content_type.take() {
                let ct_header_name = b"Content-Type";
                if !r
                    .Headers(can_gc)
                    .Has(ByteString::new(ct_header_name.to_vec()))
                    .unwrap()
                {
                    let ct_header_val = contents.as_bytes();
                    r.Headers(can_gc).Append(
                        ByteString::new(ct_header_name.to_vec()),
                        ByteString::new(ct_header_val.to_vec()),
                    )?;

                    // Step 37.4
                    // In Servo r.Headers's header list isn't a pointer to
                    // the same actual list as r.request's, and so we need to
                    // append to both lists to keep them in sync.
                    if let Ok(v) = HeaderValue::from_bytes(ct_header_val) {
                        r.request
                            .borrow_mut()
                            .headers
                            .insert(HeaderName::from_bytes(ct_header_name).unwrap(), v);
                    }
                }
            }

            let (net_body, stream) = extracted_body.into_net_request_body();
            r.body_stream.set(Some(&*stream));
            input_body = Some(net_body);
        }

        // Step 38 is done earlier

        // Step 39 "TODO if body is non-null and body's source is null..."
        // This looks like where we need to set the use-preflight flag
        // if the request has a body and nothing else has set the flag.

        // Step 40 is done earlier

        // Step 41
        r.request.borrow_mut().body = input_body;

        // Step 42
        Ok(r)
    }

    // https://fetch.spec.whatwg.org/#dom-request-method
    fn Method(&self) -> ByteString {
        let r = self.request.borrow();
        ByteString::new(r.method.as_ref().as_bytes().into())
    }

    // https://fetch.spec.whatwg.org/#dom-request-url
    fn Url(&self) -> USVString {
        let r = self.request.borrow();
        USVString(r.url_list.first().map_or("", |u| u.as_str()).into())
    }

    // https://fetch.spec.whatwg.org/#dom-request-headers
    fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
        self.headers
            .or_init(|| Headers::new(&self.global(), can_gc))
    }

    // https://fetch.spec.whatwg.org/#dom-request-destination
    fn Destination(&self) -> RequestDestination {
        self.request.borrow().destination.convert()
    }

    // https://fetch.spec.whatwg.org/#dom-request-referrer
    fn Referrer(&self) -> USVString {
        let r = self.request.borrow();
        USVString(match r.referrer {
            NetTraitsRequestReferrer::NoReferrer => String::from(""),
            NetTraitsRequestReferrer::Client(_) => String::from("about:client"),
            NetTraitsRequestReferrer::ReferrerUrl(ref u) => {
                let u_c = u.clone();
                u_c.into_string()
            },
        })
    }

    // https://fetch.spec.whatwg.org/#dom-request-referrerpolicy
    fn ReferrerPolicy(&self) -> ReferrerPolicy {
        self.request.borrow().referrer_policy.convert()
    }

    // https://fetch.spec.whatwg.org/#dom-request-mode
    fn Mode(&self) -> RequestMode {
        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.convert()
    }

    // https://fetch.spec.whatwg.org/#dom-request-cache
    fn Cache(&self) -> RequestCache {
        let r = self.request.borrow().clone();
        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.convert()
    }

    // https://fetch.spec.whatwg.org/#dom-request-integrity
    fn Integrity(&self) -> DOMString {
        let r = self.request.borrow();
        DOMString::from_string(r.integrity_metadata.clone())
    }

    /// <https://fetch.spec.whatwg.org/#dom-body-body>
    fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
        self.body()
    }

    // https://fetch.spec.whatwg.org/#dom-body-bodyused
    fn BodyUsed(&self) -> bool {
        self.is_disturbed()
    }

    // https://fetch.spec.whatwg.org/#dom-request-clone
    fn Clone(&self, can_gc: CanGc) -> Fallible<DomRoot<Request>> {
        // Step 1
        if request_is_locked(self) {
            return Err(Error::Type("Request is locked".to_string()));
        }
        if request_is_disturbed(self) {
            return Err(Error::Type("Request is disturbed".to_string()));
        }

        // Step 2
        Request::clone_from(self, can_gc)
    }

    // https://fetch.spec.whatwg.org/#dom-body-text
    fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
        consume_body(self, BodyType::Text, can_gc)
    }

    // https://fetch.spec.whatwg.org/#dom-body-blob
    fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
        consume_body(self, BodyType::Blob, can_gc)
    }

    // https://fetch.spec.whatwg.org/#dom-body-formdata
    fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
        consume_body(self, BodyType::FormData, can_gc)
    }

    // https://fetch.spec.whatwg.org/#dom-body-json
    fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
        consume_body(self, BodyType::Json, can_gc)
    }

    // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
    fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
        consume_body(self, BodyType::ArrayBuffer, can_gc)
    }

    /// <https://fetch.spec.whatwg.org/#dom-body-bytes>
    fn Bytes(&self, can_gc: CanGc) -> std::rc::Rc<Promise> {
        consume_body(self, BodyType::Bytes, can_gc)
    }
}

impl BodyMixin for Request {
    fn is_disturbed(&self) -> bool {
        let body_stream = self.body_stream.get();
        body_stream
            .as_ref()
            .is_some_and(|stream| stream.is_disturbed())
    }

    fn is_locked(&self) -> bool {
        let body_stream = self.body_stream.get();
        body_stream.is_some_and(|stream| stream.is_locked())
    }

    fn body(&self) -> Option<DomRoot<ReadableStream>> {
        self.body_stream.get()
    }

    fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8> {
        let headers = self.Headers(can_gc);
        headers.extract_mime_type()
    }
}

impl Convert<NetTraitsRequestCache> for RequestCache {
    fn convert(self) -> NetTraitsRequestCache {
        match self {
            RequestCache::Default => NetTraitsRequestCache::Default,
            RequestCache::No_store => NetTraitsRequestCache::NoStore,
            RequestCache::Reload => NetTraitsRequestCache::Reload,
            RequestCache::No_cache => NetTraitsRequestCache::NoCache,
            RequestCache::Force_cache => NetTraitsRequestCache::ForceCache,
            RequestCache::Only_if_cached => NetTraitsRequestCache::OnlyIfCached,
        }
    }
}

impl Convert<RequestCache> for NetTraitsRequestCache {
    fn convert(self) -> RequestCache {
        match self {
            NetTraitsRequestCache::Default => RequestCache::Default,
            NetTraitsRequestCache::NoStore => RequestCache::No_store,
            NetTraitsRequestCache::Reload => RequestCache::Reload,
            NetTraitsRequestCache::NoCache => RequestCache::No_cache,
            NetTraitsRequestCache::ForceCache => RequestCache::Force_cache,
            NetTraitsRequestCache::OnlyIfCached => RequestCache::Only_if_cached,
        }
    }
}

impl Convert<NetTraitsRequestCredentials> for RequestCredentials {
    fn convert(self) -> NetTraitsRequestCredentials {
        match self {
            RequestCredentials::Omit => NetTraitsRequestCredentials::Omit,
            RequestCredentials::Same_origin => NetTraitsRequestCredentials::CredentialsSameOrigin,
            RequestCredentials::Include => NetTraitsRequestCredentials::Include,
        }
    }
}

impl Convert<RequestCredentials> for NetTraitsRequestCredentials {
    fn convert(self) -> RequestCredentials {
        match self {
            NetTraitsRequestCredentials::Omit => RequestCredentials::Omit,
            NetTraitsRequestCredentials::CredentialsSameOrigin => RequestCredentials::Same_origin,
            NetTraitsRequestCredentials::Include => RequestCredentials::Include,
        }
    }
}

impl Convert<NetTraitsRequestDestination> for RequestDestination {
    fn convert(self) -> NetTraitsRequestDestination {
        match self {
            RequestDestination::_empty => NetTraitsRequestDestination::None,
            RequestDestination::Audio => NetTraitsRequestDestination::Audio,
            RequestDestination::Document => NetTraitsRequestDestination::Document,
            RequestDestination::Embed => NetTraitsRequestDestination::Embed,
            RequestDestination::Font => NetTraitsRequestDestination::Font,
            RequestDestination::Frame => NetTraitsRequestDestination::Frame,
            RequestDestination::Iframe => NetTraitsRequestDestination::IFrame,
            RequestDestination::Image => NetTraitsRequestDestination::Image,
            RequestDestination::Manifest => NetTraitsRequestDestination::Manifest,
            RequestDestination::Json => NetTraitsRequestDestination::Json,
            RequestDestination::Object => NetTraitsRequestDestination::Object,
            RequestDestination::Report => NetTraitsRequestDestination::Report,
            RequestDestination::Script => NetTraitsRequestDestination::Script,
            RequestDestination::Sharedworker => NetTraitsRequestDestination::SharedWorker,
            RequestDestination::Style => NetTraitsRequestDestination::Style,
            RequestDestination::Track => NetTraitsRequestDestination::Track,
            RequestDestination::Video => NetTraitsRequestDestination::Video,
            RequestDestination::Worker => NetTraitsRequestDestination::Worker,
            RequestDestination::Xslt => NetTraitsRequestDestination::Xslt,
        }
    }
}

impl Convert<RequestDestination> for NetTraitsRequestDestination {
    fn convert(self) -> RequestDestination {
        match self {
            NetTraitsRequestDestination::None => RequestDestination::_empty,
            NetTraitsRequestDestination::Audio => RequestDestination::Audio,
            NetTraitsRequestDestination::Document => RequestDestination::Document,
            NetTraitsRequestDestination::Embed => RequestDestination::Embed,
            NetTraitsRequestDestination::Font => RequestDestination::Font,
            NetTraitsRequestDestination::Frame => RequestDestination::Frame,
            NetTraitsRequestDestination::IFrame => RequestDestination::Iframe,
            NetTraitsRequestDestination::Image => RequestDestination::Image,
            NetTraitsRequestDestination::Manifest => RequestDestination::Manifest,
            NetTraitsRequestDestination::Json => RequestDestination::Json,
            NetTraitsRequestDestination::Object => RequestDestination::Object,
            NetTraitsRequestDestination::Report => RequestDestination::Report,
            NetTraitsRequestDestination::Script => RequestDestination::Script,
            NetTraitsRequestDestination::ServiceWorker |
            NetTraitsRequestDestination::AudioWorklet |
            NetTraitsRequestDestination::PaintWorklet => {
                panic!("ServiceWorker request destination should not be exposed to DOM")
            },
            NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker,
            NetTraitsRequestDestination::Style => RequestDestination::Style,
            NetTraitsRequestDestination::Track => RequestDestination::Track,
            NetTraitsRequestDestination::Video => RequestDestination::Video,
            NetTraitsRequestDestination::Worker => RequestDestination::Worker,
            NetTraitsRequestDestination::Xslt => RequestDestination::Xslt,
            NetTraitsRequestDestination::WebIdentity => RequestDestination::_empty,
        }
    }
}

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,
            RequestMode::Cors => NetTraitsRequestMode::CorsMode,
        }
    }
}

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,
            NetTraitsRequestMode::CorsMode => RequestMode::Cors,
            NetTraitsRequestMode::WebSocket { .. } => {
                unreachable!("Websocket request mode should never be exposed to Dom")
            },
        }
    }
}

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 => {
                MsgReferrerPolicy::NoReferrerWhenDowngrade
            },
            ReferrerPolicy::Origin => MsgReferrerPolicy::Origin,
            ReferrerPolicy::Origin_when_cross_origin => MsgReferrerPolicy::OriginWhenCrossOrigin,
            ReferrerPolicy::Unsafe_url => MsgReferrerPolicy::UnsafeUrl,
            ReferrerPolicy::Same_origin => MsgReferrerPolicy::SameOrigin,
            ReferrerPolicy::Strict_origin => MsgReferrerPolicy::StrictOrigin,
            ReferrerPolicy::Strict_origin_when_cross_origin => {
                MsgReferrerPolicy::StrictOriginWhenCrossOrigin
            },
        }
    }
}

impl Convert<ReferrerPolicy> for MsgReferrerPolicy {
    fn convert(self) -> ReferrerPolicy {
        match self {
            MsgReferrerPolicy::EmptyString => ReferrerPolicy::_empty,
            MsgReferrerPolicy::NoReferrer => ReferrerPolicy::No_referrer,
            MsgReferrerPolicy::NoReferrerWhenDowngrade => {
                ReferrerPolicy::No_referrer_when_downgrade
            },
            MsgReferrerPolicy::Origin => ReferrerPolicy::Origin,
            MsgReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicy::Origin_when_cross_origin,
            MsgReferrerPolicy::UnsafeUrl => ReferrerPolicy::Unsafe_url,
            MsgReferrerPolicy::SameOrigin => ReferrerPolicy::Same_origin,
            MsgReferrerPolicy::StrictOrigin => ReferrerPolicy::Strict_origin,
            MsgReferrerPolicy::StrictOriginWhenCrossOrigin => {
                ReferrerPolicy::Strict_origin_when_cross_origin
            },
        }
    }
}

impl Convert<NetTraitsRequestRedirect> for RequestRedirect {
    fn convert(self) -> NetTraitsRequestRedirect {
        match self {
            RequestRedirect::Follow => NetTraitsRequestRedirect::Follow,
            RequestRedirect::Error => NetTraitsRequestRedirect::Error,
            RequestRedirect::Manual => NetTraitsRequestRedirect::Manual,
        }
    }
}

impl Convert<RequestRedirect> for NetTraitsRequestRedirect {
    fn convert(self) -> RequestRedirect {
        match self {
            NetTraitsRequestRedirect::Follow => RequestRedirect::Follow,
            NetTraitsRequestRedirect::Error => RequestRedirect::Error,
            NetTraitsRequestRedirect::Manual => RequestRedirect::Manual,
        }
    }
}