Bug 1631722 - Add wrapper for structured-headers crate,r=valentin
authorundef1nd <yalyna.ts@gmail.com>
Wed, 12 Aug 2020 07:07:33 +0000
changeset 544368 203dd164f9cace9472671d4f63350c31f856ecb0
parent 544367 cfa1f8e3a900ae38338c1041159cd6d36afde32c
child 544369 0f17312b01adca7d9d7032cce5f1d1170d424302
push id123994
push uservalentin.gosu@gmail.com
push dateWed, 12 Aug 2020 07:12:30 +0000
treeherderautoland@203dd164f9ca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1631722
milestone81.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1631722 - Add wrapper for structured-headers crate,r=valentin Differential Revision: https://phabricator.services.mozilla.com/D81127
Cargo.lock
netwerk/base/http-sfv/Cargo.toml
netwerk/base/http-sfv/SFVService.cpp
netwerk/base/http-sfv/SFVService.h
netwerk/base/http-sfv/moz.build
netwerk/base/http-sfv/nsIStructuredFieldValues.idl
netwerk/base/http-sfv/src/lib.rs
netwerk/base/moz.build
netwerk/build/components.conf
netwerk/test/unit/test_http_sfv.js
netwerk/test/unit/xpcshell.ini
toolkit/library/rust/shared/Cargo.toml
toolkit/library/rust/shared/lib.rs
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1973,16 +1973,17 @@ dependencies = [
  "fluent",
  "fluent-ffi",
  "fluent-langneg",
  "fluent-langneg-ffi",
  "fog_control",
  "gecko_logger",
  "geckoservo",
  "gkrust_utils",
+ "http_sfv",
  "jsrust_shared",
  "kvstore",
  "lmdb-rkv-sys",
  "log",
  "mapped_hyph",
  "mdns_service",
  "mozurl",
  "mp4parse_capi",
@@ -2257,16 +2258,27 @@ dependencies = [
  "neqo-common",
  "neqo-crypto",
  "neqo-http3",
  "neqo-qpack",
  "neqo-transport",
 ]
 
 [[package]]
+name = "http_sfv"
+version = "0.1.0"
+dependencies = [
+ "nserror",
+ "nsstring",
+ "sfv",
+ "thin-vec",
+ "xpcom",
+]
+
+[[package]]
 name = "httparse"
 version = "1.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
 
 [[package]]
 name = "humantime"
 version = "1.3.0"
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "http_sfv"
+version = "0.1.0"
+authors = ["barabass <yalyna.ts@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+sfv = "0.8.0"
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.1.0", features = ["gecko-ffi"] }
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "SFVService.h"
+
+// This anonymous namespace prevents outside C++ code from improperly accessing
+// these implementation details.
+namespace {
+extern "C" {
+// Implemented in Rust.
+void new_sfv_service(nsISFVService** result);
+}
+
+static mozilla::StaticRefPtr<nsISFVService> sService;
+}  // namespace
+
+namespace mozilla::net {
+
+already_AddRefed<nsISFVService> GetSFVService() {
+  nsCOMPtr<nsISFVService> service;
+
+  if (sService) {
+    service = sService;
+  } else {
+    new_sfv_service(getter_AddRefs(service));
+    sService = service;
+    mozilla::ClearOnShutdown(&sService);
+  }
+
+  return service.forget();
+}
+
+}  // namespace mozilla::net
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStructuredFieldValues.h"
+namespace mozilla {
+namespace net {
+
+already_AddRefed<nsISFVService> GetSFVService();
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+    'nsIStructuredFieldValues.idl',
+]
+
+XPIDL_MODULE = 'http-sfv'
+
+EXPORTS.mozilla.net += ['SFVService.h']
+
+SOURCES += ['SFVService.cpp']
+
+FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl
@@ -0,0 +1,290 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Conceptually, there are three types of structured field (header) values:
+ *
+ * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean.
+ * It can have associated Parameters.
+ * List - array of zero or more members, each of which can be an Item or an InnerList,
+ * both of which can be Parameterized.
+ * Dictionary - ordered map of name-value pairs, where the names are short textual strings
+ * and the values are Items or arrays of Items (represented with InnerList),
+ * both of which can be Parameterized. There can be zero or more members,
+ * and their names are unique in the scope of the Dictionary they occur within.
+ *
+ *
+ * There's also a few primitive types used to construct structured field values:
+ * - BareItem used as Item's value or as a parameter value in Parameters.
+ * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList.
+ * The keys are unique within the scope the Parameters they occur within, and the values are BareItem.
+ * - InnerList is an array of zero or more Items. Can have Parameters.
+ * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary.
+ */
+
+
+
+/**
+ * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams).
+ * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE.
+ * Each type is represented by its own interface which is used to create
+ * a bare item of that type.
+ */
+[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)]
+interface nsISFVBareItem : nsISupports {
+    const long BOOL = 1;
+    const long STRING = 2;
+    const long DECIMAL = 3;
+    const long INTEGER = 4;
+    const long TOKEN = 5;
+    const long BYTE_SEQUENCE = 6;
+
+    /**
+     * Returns value associated with type of bare item.
+     * Used to identify type of bare item without querying for interface
+     * (like nsISFVString, etc).
+     */
+    readonly attribute long long type;
+};
+
+[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)]
+interface nsISFVInteger : nsISFVBareItem {
+    attribute long long value;
+};
+
+[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)]
+interface nsISFVString : nsISFVBareItem {
+    attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)]
+interface nsISFVBool : nsISFVBareItem {
+    attribute boolean value;
+};
+
+[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)]
+interface nsISFVDecimal : nsISFVBareItem {
+    attribute double value;
+};
+
+[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)]
+interface nsISFVToken : nsISFVBareItem {
+    attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)]
+interface nsISFVByteSeq : nsISFVBareItem {
+    attribute ACString value;
+};
+
+
+/**
+ * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem,
+ * which parametrize Item type header or InnerList type withing List type header.
+ */
+[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)]
+interface nsISFVParams : nsISupports {
+    /**
+     * Return value (nsISFVBareItem) stored for key, if it is present
+     *
+     * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+     */
+    nsISFVBareItem get(in ACString key);
+
+    /**
+     * Insert a new key-value pair.
+     * If an equivalent key already exists: the key remains and retains in its place in the order,
+     * its corresponding value is updated with the new value.
+     *
+     * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface.
+     */
+    void set(in ACString key, in nsISFVBareItem item);
+
+    /**
+     * Remove the key-value pair equivalent to key.
+     *
+     * @throws NS_ERROR_UNEXPECTED upon attempt to delete  key that does not exist in parameters.
+     */
+    void delete(in ACString key);
+
+    /**
+     * Returns array of keys.
+     */
+    Array<ACString> keys();
+};
+
+/**
+ * nsISFVParametrizable is implemented for types that
+ * can be parametrized with nsISFVParams
+ */
+[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)]
+interface nsISFVParametrizable : nsISupports {
+    readonly attribute nsISFVParams params;
+};
+
+/**
+ * nsISFVItemOrInnerList represents member in nsISFVList
+ * or member-value in nsISFVDictionary.
+ * nsISFVItemOrInnerList is implemented for
+ * nsISFVItem or nsISFVInnerList, both of which are used
+ * to create nsISFVList and nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)]
+interface nsISFVItemOrInnerList : nsISFVParametrizable {
+};
+
+/**
+ * nsISFVSerialize indicates that object can be serialized into ACString.
+ */
+[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)]
+interface nsISFVSerialize : nsISupports {
+    ACString serialize();
+};
+
+/**
+ * nsISFVItem represents Item structured header value.
+ */
+[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)]
+interface nsISFVItem : nsISFVItemOrInnerList {
+    readonly attribute nsISFVBareItem value;
+    ACString serialize();
+};
+
+/**
+ * nsISFVInnerList can be used as a member of nsISFVList
+ * or a member-value of nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)]
+interface nsISFVInnerList : nsISFVItemOrInnerList {
+    attribute Array<nsISFVItem> items;
+};
+
+/**
+ * nsISFVList represents List structured header value.
+ */
+[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)]
+interface nsISFVList : nsISFVSerialize {
+    /**
+     * Returns array of members.
+     * QueryInterface can be used on a member to get more specific type.
+     */
+    attribute Array<nsISFVItemOrInnerList> members;
+
+    /**
+     * In case when header value is split across lines, it's possible
+     * this method parses supplied line and merges it with members of existing object.
+     */
+    void parseMore(in ACString header);
+};
+
+/**
+ * nsISFVDictionary represents nsISFVDictionary structured header value.
+ */
+[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)]
+interface nsISFVDictionary : nsISFVSerialize {
+
+    /**
+     * Return value (nsISFVItemOrInnerList) stored for key, if it is present.
+     * QueryInterface can be used on a value to get more specific type.
+     *
+     * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+     */
+    nsISFVItemOrInnerList get(in ACString key);
+
+    /**
+     * Insert a new key-value pair.
+     * If an equivalent key already exists: the key remains and retains in its place in the order,
+     * its corresponding value is updated with the new value.
+     *
+     * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface.
+     */
+    void set(in ACString key, in nsISFVItemOrInnerList member_value);
+
+    /**
+     * Remove the key-value pair equivalent to key.
+     *
+     * @throws NS_ERROR_UNEXPECTED upon attempt to delete  key that does not exist in parameters.
+     */
+    void delete(in ACString key);
+
+    /**
+     * Returns array of keys.
+     */
+    Array<ACString> keys();
+
+    /**
+     * In case when header value is split across lines, it's possible
+     * this method parses supplied line and merges it with members of existing object.
+     */
+    void parseMore(in ACString header);
+};
+
+
+/**
+ * nsISFVService provides a set of functions for working with HTTP header value as an object.
+ * It exposes functions for creating object from string containing header value,
+ * as well as individual components for manual structured header object creation.
+ */
+[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)]
+interface nsISFVService: nsISupports
+{
+    /**
+     * Parses provided string into Dictionary header value (nsISFVDictionary).
+     *
+     * @throws NS_ERROR_FAILURE if parsing fails.
+     */
+    nsISFVDictionary parseDictionary(in ACString header);
+
+    /**
+     * Parses provided string into List header value (nsISFVList).
+     *
+     * @throws NS_ERROR_FAILURE if parsing fails.
+     */
+    nsISFVList parseList(in ACString header);
+
+    /**
+     * Parses provided string into Item header value (nsISFVItem).
+     *
+     * @throws NS_ERROR_FAILURE if parsing fails.
+     */
+    nsISFVItem parseItem(in ACString header);
+
+    /**
+     * The following functions create bare item of specific type.
+     */
+    nsISFVInteger newInteger(in long long value);
+    nsISFVBool newBool(in bool value);
+    nsISFVDecimal newDecimal(in double value);
+    nsISFVString newString(in ACString value);
+    nsISFVByteSeq newByteSequence(in ACString value);
+    nsISFVToken newToken(in ACString value);
+
+    /**
+     * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default.
+     */
+    nsISFVParams newParameters();
+
+    /**
+     * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams.
+     */
+    nsISFVInnerList newInnerList(in Array<nsISFVItem> items, in nsISFVParams params);
+
+    /**
+     * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams.
+     */
+    nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params);
+
+    /**
+     * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList.
+     * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList).
+     */
+    nsISFVList newList(in Array<nsISFVItemOrInnerList> members);
+
+    /**
+     * Creates nsISFVDictionary representing Dictionary header value. It is empty by default.
+     */
+    nsISFVDictionary newDictionary();
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/http-sfv/src/lib.rs
@@ -0,0 +1,870 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use sfv::Parser;
+use sfv::SerializeValue;
+use sfv::{
+    BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters,
+    ParseMore,
+};
+use std::cell::RefCell;
+use std::ops::Deref;
+use thin_vec::ThinVec;
+use xpcom::interfaces::{
+    nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList,
+    nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService,
+    nsISFVString, nsISFVToken,
+};
+use xpcom::{xpcom, xpcom_method, RefPtr, XpCom};
+
+#[no_mangle]
+pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) {
+    let service: RefPtr<SFVService> = SFVService::new();
+    RefPtr::new(service.coerce::<nsISFVService>()).forget(&mut *result);
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVService)]
+#[refcnt = "atomic"]
+struct InitSFVService {}
+
+impl SFVService {
+    fn new() -> RefPtr<SFVService> {
+        SFVService::allocate(InitSFVService {})
+    }
+
+    xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary);
+    fn parse_dictionary(&self, header: &nsACString) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+        let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?;
+        let sfv_dict = SFVDictionary::new();
+        sfv_dict.value.replace(parsed_dict);
+        Ok(RefPtr::new(sfv_dict.coerce::<nsISFVDictionary>()))
+    }
+
+    xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList);
+    fn parse_list(&self, header: &nsACString) -> Result<RefPtr<nsISFVList>, nsresult> {
+        let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+        let mut nsi_members = ThinVec::new();
+        for item_or_inner_list in parsed_list.iter() {
+            nsi_members.push(interface_from_list_entry(item_or_inner_list)?)
+        }
+        let sfv_list = SFVList::allocate(InitSFVList {
+            members: RefCell::new(nsi_members),
+        });
+        Ok(RefPtr::new(sfv_list.coerce::<nsISFVList>()))
+    }
+
+    xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem);
+    fn parse_item(&self, header: &nsACString) -> Result<RefPtr<nsISFVItem>, nsresult> {
+        let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?;
+        interface_from_item(&parsed_item)
+    }
+
+    xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger);
+    fn new_integer(&self, value: i64) -> Result<RefPtr<nsISFVInteger>, nsresult> {
+        Ok(RefPtr::new(
+            SFVInteger::new(value).coerce::<nsISFVInteger>(),
+        ))
+    }
+
+    xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal);
+    fn new_decimal(&self, value: f64) -> Result<RefPtr<nsISFVDecimal>, nsresult> {
+        Ok(RefPtr::new(
+            SFVDecimal::new(value).coerce::<nsISFVDecimal>(),
+        ))
+    }
+
+    xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool);
+    fn new_bool(&self, value: bool) -> Result<RefPtr<nsISFVBool>, nsresult> {
+        Ok(RefPtr::new(SFVBool::new(value).coerce::<nsISFVBool>()))
+    }
+
+    xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString);
+    fn new_string(&self, value: &nsACString) -> Result<RefPtr<nsISFVString>, nsresult> {
+        Ok(RefPtr::new(SFVString::new(value).coerce::<nsISFVString>()))
+    }
+
+    xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken);
+    fn new_token(&self, value: &nsACString) -> Result<RefPtr<nsISFVToken>, nsresult> {
+        Ok(RefPtr::new(SFVToken::new(value).coerce::<nsISFVToken>()))
+    }
+
+    xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq);
+    fn new_byte_sequence(&self, value: &nsACString) -> Result<RefPtr<nsISFVByteSeq>, nsresult> {
+        Ok(RefPtr::new(
+            SFVByteSeq::new(value).coerce::<nsISFVByteSeq>(),
+        ))
+    }
+
+    xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams);
+    fn new_parameters(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+        Ok(RefPtr::new(SFVParams::new().coerce::<nsISFVParams>()))
+    }
+
+    xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params:  *const nsISFVParams) -> *const nsISFVItem);
+    fn new_item(
+        &self,
+        value: &nsISFVBareItem,
+        params: &nsISFVParams,
+    ) -> Result<RefPtr<nsISFVItem>, nsresult> {
+        Ok(RefPtr::new(
+            SFVItem::new(value, params).coerce::<nsISFVItem>(),
+        ))
+    }
+
+    xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec<RefPtr<nsISFVItem>>, params:  *const nsISFVParams) -> *const nsISFVInnerList);
+    fn new_inner_list(
+        &self,
+        items: &thin_vec::ThinVec<RefPtr<nsISFVItem>>,
+        params: &nsISFVParams,
+    ) -> Result<RefPtr<nsISFVInnerList>, nsresult> {
+        Ok(RefPtr::new(
+            SFVInnerList::new(items, params).coerce::<nsISFVInnerList>(),
+        ))
+    }
+
+    xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>) -> *const nsISFVList);
+    fn new_list(
+        &self,
+        members: &thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>,
+    ) -> Result<RefPtr<nsISFVList>, nsresult> {
+        Ok(RefPtr::new(SFVList::new(members).coerce::<nsISFVList>()))
+    }
+
+    xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary);
+    fn new_dictionary(&self) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+        Ok(RefPtr::new(
+            SFVDictionary::new().coerce::<nsISFVDictionary>(),
+        ))
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVInteger, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVInteger {
+    value: RefCell<i64>,
+}
+
+impl SFVInteger {
+    fn new(value: i64) -> RefPtr<SFVInteger> {
+        SFVInteger::allocate(InitSFVInteger {
+            value: RefCell::new(value),
+        })
+    }
+
+    xpcom_method!(get_value => GetValue() -> i64);
+    fn get_value(&self) -> Result<i64, nsresult> {
+        Ok(*self.value.borrow())
+    }
+
+    xpcom_method!(set_value => SetValue(value: i64));
+    fn set_value(&self, value: i64) -> Result<(), nsresult> {
+        self.value.replace(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::INTEGER)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVBool, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVBool {
+    value: RefCell<bool>,
+}
+
+impl SFVBool {
+    fn new(value: bool) -> RefPtr<SFVBool> {
+        SFVBool::allocate(InitSFVBool {
+            value: RefCell::new(value),
+        })
+    }
+
+    xpcom_method!(get_value => GetValue() -> bool);
+    fn get_value(&self) -> Result<bool, nsresult> {
+        Ok(*self.value.borrow())
+    }
+
+    xpcom_method!(set_value => SetValue(value: bool));
+    fn set_value(&self, value: bool) -> Result<(), nsresult> {
+        self.value.replace(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::BOOL)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVString, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVString {
+    value: RefCell<nsCString>,
+}
+
+impl SFVString {
+    fn new(value: &nsACString) -> RefPtr<SFVString> {
+        SFVString::allocate(InitSFVString {
+            value: RefCell::new(nsCString::from(value)),
+        })
+    }
+
+    xpcom_method!(
+        get_value => GetValue(
+        ) -> nsACString
+    );
+
+    fn get_value(&self) -> Result<nsCString, nsresult> {
+        Ok(self.value.borrow().clone())
+    }
+
+    xpcom_method!(
+        set_value => SetValue(value: *const nsACString)
+    );
+
+    fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+        self.value.borrow_mut().assign(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::STRING)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVToken, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVToken {
+    value: RefCell<nsCString>,
+}
+
+impl SFVToken {
+    fn new(value: &nsACString) -> RefPtr<SFVToken> {
+        SFVToken::allocate(InitSFVToken {
+            value: RefCell::new(nsCString::from(value)),
+        })
+    }
+
+    xpcom_method!(
+        get_value => GetValue(
+        ) -> nsACString
+    );
+
+    fn get_value(&self) -> Result<nsCString, nsresult> {
+        Ok(self.value.borrow().clone())
+    }
+
+    xpcom_method!(
+        set_value => SetValue(value: *const nsACString)
+    );
+
+    fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+        self.value.borrow_mut().assign(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::TOKEN)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVByteSeq, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVByteSeq {
+    value: RefCell<nsCString>,
+}
+
+impl SFVByteSeq {
+    fn new(value: &nsACString) -> RefPtr<SFVByteSeq> {
+        SFVByteSeq::allocate(InitSFVByteSeq {
+            value: RefCell::new(nsCString::from(value)),
+        })
+    }
+
+    xpcom_method!(
+        get_value => GetValue(
+        ) -> nsACString
+    );
+
+    fn get_value(&self) -> Result<nsCString, nsresult> {
+        Ok(self.value.borrow().clone())
+    }
+
+    xpcom_method!(
+        set_value => SetValue(value: *const nsACString)
+    );
+
+    fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+        self.value.borrow_mut().assign(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::BYTE_SEQUENCE)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVDecimal, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVDecimal {
+    value: RefCell<f64>,
+}
+
+impl SFVDecimal {
+    fn new(value: f64) -> RefPtr<SFVDecimal> {
+        SFVDecimal::allocate(InitSFVDecimal {
+            value: RefCell::new(value),
+        })
+    }
+
+    xpcom_method!(
+        get_value => GetValue(
+        ) -> f64
+    );
+
+    fn get_value(&self) -> Result<f64, nsresult> {
+        Ok(*self.value.borrow())
+    }
+
+    xpcom_method!(
+        set_value => SetValue(value: f64)
+    );
+
+    fn set_value(&self, value: f64) -> Result<(), nsresult> {
+        self.value.replace(value);
+        Ok(())
+    }
+
+    xpcom_method!(get_type => GetType() -> i64);
+    fn get_type(&self) -> Result<i64, nsresult> {
+        Ok(nsISFVBareItem::DECIMAL)
+    }
+
+    fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVParams)]
+#[refcnt = "nonatomic"]
+struct InitSFVParams {
+    params: RefCell<Parameters>,
+}
+
+impl SFVParams {
+    fn new() -> RefPtr<SFVParams> {
+        SFVParams::allocate(InitSFVParams {
+            params: RefCell::new(Parameters::new()),
+        })
+    }
+
+    xpcom_method!(
+        get => Get(key: *const nsACString) -> *const nsISFVBareItem
+    );
+
+    fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+        let key = key.to_utf8();
+        let params = self.params.borrow();
+        let param_val = params.get(key.as_ref());
+
+        match param_val {
+            Some(val) => interface_from_bare_item(val),
+            None => return Err(NS_ERROR_UNEXPECTED),
+        }
+    }
+
+    xpcom_method!(
+        set => Set(key: *const nsACString, item: *const nsISFVBareItem)
+    );
+
+    fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> {
+        let key = key.to_utf8().into_owned();
+        let bare_item = bare_item_from_interface(item)?;
+        self.params.borrow_mut().insert(key, bare_item);
+        Ok(())
+    }
+
+    xpcom_method!(
+        delete => Delete(key: *const nsACString)
+    );
+    fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+        let key = key.to_utf8();
+        let mut params = self.params.borrow_mut();
+
+        if !params.contains_key(key.as_ref()) {
+            return Err(NS_ERROR_UNEXPECTED);
+        }
+
+        // Keeps only entries that don't match key
+        params.retain(|k, _| k != key.as_ref());
+        Ok(())
+    }
+
+    xpcom_method!(
+        keys => Keys() -> thin_vec::ThinVec<nsCString>
+    );
+    fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+        let keys = self.params.borrow();
+        let keys = keys
+            .keys()
+            .map(nsCString::from)
+            .collect::<ThinVec<nsCString>>();
+        Ok(keys)
+    }
+
+    fn from_interface(obj: &nsISFVParams) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVItem, nsISFVItemOrInnerList)]
+#[refcnt = "nonatomic"]
+struct InitSFVItem {
+    value: RefPtr<nsISFVBareItem>,
+    params: RefPtr<nsISFVParams>,
+}
+
+impl SFVItem {
+    fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr<SFVItem> {
+        SFVItem::allocate(InitSFVItem {
+            value: RefPtr::new(value),
+            params: RefPtr::new(params),
+        })
+    }
+
+    xpcom_method!(
+        get_value => GetValue(
+        ) -> *const nsISFVBareItem
+    );
+
+    fn get_value(&self) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+        Ok(self.value.clone())
+    }
+
+    xpcom_method!(
+        get_params => GetParams(
+        ) -> *const nsISFVParams
+    );
+    fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+        Ok(self.params.clone())
+    }
+
+    xpcom_method!(
+        serialize => Serialize() -> nsACString
+    );
+    fn serialize(&self) -> Result<nsCString, nsresult> {
+        let bare_item = bare_item_from_interface(self.value.deref())?;
+        let params = params_from_interface(self.params.deref())?;
+        let serialized = Item::with_params(bare_item, params)
+            .serialize_value()
+            .map_err(|_| NS_ERROR_FAILURE)?;
+        Ok(nsCString::from(serialized))
+    }
+
+    fn from_interface(obj: &nsISFVItem) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVInnerList, nsISFVItemOrInnerList)]
+#[refcnt = "nonatomic"]
+struct InitSFVInnerList {
+    items: RefCell<thin_vec::ThinVec<RefPtr<nsISFVItem>>>,
+    params: RefPtr<nsISFVParams>,
+}
+
+impl SFVInnerList {
+    fn new(
+        items: &thin_vec::ThinVec<RefPtr<nsISFVItem>>,
+        params: &nsISFVParams,
+    ) -> RefPtr<SFVInnerList> {
+        SFVInnerList::allocate(InitSFVInnerList {
+            items: RefCell::new((*items).clone()),
+            params: RefPtr::new(params),
+        })
+    }
+
+    xpcom_method!(
+        get_items => GetItems() -> thin_vec::ThinVec<RefPtr<nsISFVItem>>
+    );
+
+    fn get_items(&self) -> Result<thin_vec::ThinVec<RefPtr<nsISFVItem>>, nsresult> {
+        let items = self.items.borrow().clone();
+        Ok(items)
+    }
+
+    #[allow(non_snake_case)]
+    unsafe fn SetItems(&self, value: *const thin_vec::ThinVec<RefPtr<nsISFVItem>>) -> nsresult {
+        if value.is_null() {
+            return NS_ERROR_NULL_POINTER;
+        }
+        *self.items.borrow_mut() = (*value).clone();
+        NS_OK
+    }
+
+    xpcom_method!(
+        get_params => GetParams(
+        ) -> *const nsISFVParams
+    );
+    fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+        Ok(self.params.clone())
+    }
+
+    fn from_interface(obj: &nsISFVInnerList) -> &Self {
+        unsafe { ::std::mem::transmute(obj) }
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVList, nsISFVSerialize)]
+#[refcnt = "nonatomic"]
+struct InitSFVList {
+    members: RefCell<thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>>,
+}
+
+impl SFVList {
+    fn new(members: &thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>) -> RefPtr<SFVList> {
+        SFVList::allocate(InitSFVList {
+            members: RefCell::new((*members).clone()),
+        })
+    }
+
+    xpcom_method!(
+        get_members => GetMembers() -> thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>
+    );
+
+    fn get_members(&self) -> Result<thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>, nsresult> {
+        Ok(self.members.borrow().clone())
+    }
+
+    #[allow(non_snake_case)]
+    unsafe fn SetMembers(
+        &self,
+        value: *const thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>,
+    ) -> nsresult {
+        if value.is_null() {
+            return NS_ERROR_NULL_POINTER;
+        }
+        *self.members.borrow_mut() = (*value).clone();
+        NS_OK
+    }
+
+    xpcom_method!(
+        parse_more => ParseMore(header: *const nsACString)
+    );
+    fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+        // create List from SFVList to call parse_more on it
+        let mut list = List::new();
+        for interface_entry in self.get_members()?.iter() {
+            let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+            list.push(item_or_inner_list);
+        }
+
+        let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+        // replace SFVList's members with new_members
+        let mut new_members = ThinVec::new();
+        for item_or_inner_list in list.iter() {
+            new_members.push(interface_from_list_entry(item_or_inner_list)?)
+        }
+        self.members.replace(new_members);
+        Ok(())
+    }
+
+    xpcom_method!(
+        serialize => Serialize() -> nsACString
+    );
+    fn serialize(&self) -> Result<nsCString, nsresult> {
+        let mut list = List::new();
+
+        for interface_entry in self.get_members()?.iter() {
+            let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+            list.push(item_or_inner_list);
+        }
+
+        let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?;
+        Ok(nsCString::from(serialized))
+    }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVDictionary, nsISFVSerialize)]
+#[refcnt = "nonatomic"]
+struct InitSFVDictionary {
+    value: RefCell<Dictionary>,
+}
+
+impl SFVDictionary {
+    fn new() -> RefPtr<SFVDictionary> {
+        SFVDictionary::allocate(InitSFVDictionary {
+            value: RefCell::new(Dictionary::new()),
+        })
+    }
+
+    xpcom_method!(
+        get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList
+    );
+
+    fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+        let key = key.to_utf8();
+        let value = self.value.borrow();
+        let member_value = value.get(key.as_ref());
+
+        match member_value {
+            Some(member) => interface_from_list_entry(member),
+            None => return Err(NS_ERROR_UNEXPECTED),
+        }
+    }
+
+    xpcom_method!(
+        set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList)
+    );
+
+    fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> {
+        let key = key.to_utf8().into_owned();
+        let value = list_entry_from_interface(member_value)?;
+        self.value.borrow_mut().insert(key, value);
+        Ok(())
+    }
+
+    xpcom_method!(
+        delete => Delete(key: *const nsACString)
+    );
+
+    fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+        let key = key.to_utf8();
+        let mut params = self.value.borrow_mut();
+
+        if !params.contains_key(key.as_ref()) {
+            return Err(NS_ERROR_UNEXPECTED);
+        }
+
+        // Keeps only entries that don't match key
+        params.retain(|k, _| k != key.as_ref());
+        Ok(())
+    }
+
+    xpcom_method!(
+        keys => Keys() -> thin_vec::ThinVec<nsCString>
+    );
+    fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+        let members = self.value.borrow();
+        let keys = members
+            .keys()
+            .map(nsCString::from)
+            .collect::<ThinVec<nsCString>>();
+        Ok(keys)
+    }
+
+    xpcom_method!(
+        parse_more => ParseMore(header: *const nsACString)
+    );
+    fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+        let _ = self
+            .value
+            .borrow_mut()
+            .parse_more(&header)
+            .map_err(|_| NS_ERROR_FAILURE)?;
+        Ok(())
+    }
+
+    xpcom_method!(
+        serialize => Serialize() -> nsACString
+    );
+    fn serialize(&self) -> Result<nsCString, nsresult> {
+        let serialized = self
+            .value
+            .borrow()
+            .serialize_value()
+            .map_err(|_| NS_ERROR_FAILURE)?;
+        Ok(nsCString::from(serialized))
+    }
+}
+
+fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result<BareItem, nsresult> {
+    let obj = obj
+        .query_interface::<nsISFVBareItem>()
+        .ok_or(NS_ERROR_UNEXPECTED)?;
+    let mut obj_type: i64 = -1;
+    unsafe {
+        obj.deref().GetType(&mut obj_type);
+    }
+
+    match obj_type {
+        nsISFVBareItem::BOOL => {
+            let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?;
+            Ok(BareItem::Boolean(item_value))
+        }
+        nsISFVBareItem::STRING => {
+            let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?;
+            let item_value = (*string_itm.to_utf8()).to_string();
+            Ok(BareItem::String(item_value))
+        }
+        nsISFVBareItem::TOKEN => {
+            let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?;
+            let item_value = (*token_itm.to_utf8()).to_string();
+            Ok(BareItem::Token(item_value))
+        }
+        nsISFVBareItem::INTEGER => {
+            let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?;
+            Ok(BareItem::Integer(item_value))
+        }
+        nsISFVBareItem::DECIMAL => {
+            let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?;
+            let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?;
+            Ok(BareItem::Decimal(decimal))
+        }
+        nsISFVBareItem::BYTE_SEQUENCE => {
+            let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?;
+            let item_value: String = (*token_itm.to_utf8()).to_string();
+            Ok(BareItem::ByteSeq(item_value.into_bytes()))
+        }
+        _ => return Err(NS_ERROR_UNEXPECTED),
+    }
+}
+
+fn params_from_interface(obj: &nsISFVParams) -> Result<Parameters, nsresult> {
+    let params = SFVParams::from_interface(obj).params.borrow();
+    Ok(params.clone())
+}
+
+fn item_from_interface(obj: &nsISFVItem) -> Result<Item, nsresult> {
+    let sfv_item = SFVItem::from_interface(obj);
+    let bare_item = bare_item_from_interface(sfv_item.value.deref())?;
+    let parameters = params_from_interface(sfv_item.params.deref())?;
+    Ok(Item::with_params(bare_item, parameters))
+}
+
+fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result<InnerList, nsresult> {
+    let sfv_inner_list = SFVInnerList::from_interface(obj);
+
+    let mut inner_list_items: Vec<Item> = vec![];
+    for item in sfv_inner_list.items.borrow().iter() {
+        let item = item_from_interface(item)?;
+        inner_list_items.push(item);
+    }
+    let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?;
+    Ok(InnerList::with_params(inner_list_items, inner_list_params))
+}
+
+fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result<ListEntry, nsresult> {
+    if let Some(nsi_item) = obj.query_interface::<nsISFVItem>() {
+        let item = item_from_interface(nsi_item.deref())?;
+        Ok(ListEntry::Item(item))
+    } else if let Some(nsi_inner_list) = obj.query_interface::<nsISFVInnerList>() {
+        let inner_list = inner_list_from_interface(nsi_inner_list.deref())?;
+        Ok(ListEntry::InnerList(inner_list))
+    } else {
+        return Err(NS_ERROR_UNEXPECTED);
+    }
+}
+
+fn interface_from_bare_item(bare_item: &BareItem) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+    let bare_item = match bare_item {
+        BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::<nsISFVBareItem>()),
+        BareItem::String(val) => {
+            RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+        }
+        BareItem::Token(val) => {
+            RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+        }
+        BareItem::ByteSeq(val) => RefPtr::new(
+            SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap()))
+                .coerce::<nsISFVBareItem>(),
+        ),
+        BareItem::Decimal(val) => {
+            let val = val
+                .to_string()
+                .parse::<f64>()
+                .map_err(|_| NS_ERROR_UNEXPECTED)?;
+            RefPtr::new(SFVDecimal::new(val).coerce::<nsISFVBareItem>())
+        }
+        BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::<nsISFVBareItem>()),
+    };
+
+    Ok(bare_item)
+}
+
+fn interface_from_item(item: &Item) -> Result<RefPtr<nsISFVItem>, nsresult> {
+    let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+    let nsi_params = interface_from_params(&item.params)?;
+    Ok(RefPtr::new(
+        SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItem>(),
+    ))
+}
+
+fn interface_from_params(params: &Parameters) -> Result<RefPtr<nsISFVParams>, nsresult> {
+    let sfv_params = SFVParams::new();
+    for (key, value) in params.iter() {
+        sfv_params
+            .params
+            .borrow_mut()
+            .insert(key.clone(), value.clone());
+    }
+    Ok(RefPtr::new(sfv_params.coerce::<nsISFVParams>()))
+}
+
+fn interface_from_list_entry(
+    member: &ListEntry,
+) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+    match member {
+        ListEntry::Item(item) => {
+            let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+            let nsi_params = interface_from_params(&item.params)?;
+            Ok(RefPtr::new(
+                SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+            ))
+        }
+        ListEntry::InnerList(inner_list) => {
+            let mut nsi_inner_list = ThinVec::new();
+            for item in inner_list.items.iter() {
+                let nsi_item = interface_from_item(item)?;
+                nsi_inner_list.push(nsi_item);
+            }
+
+            let nsi_params = interface_from_params(&inner_list.params)?;
+            Ok(RefPtr::new(
+                SFVInnerList::new(&nsi_inner_list, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+            ))
+        }
+    }
+}
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -307,17 +307,17 @@ elif CONFIG['OS_ARCH'] == 'Linux':
         'NetworkInfoServiceLinux.cpp',
         'nsNetworkInfoService.cpp',
     ]
 
 EXTRA_JS_MODULES += [
     'NetUtil.jsm',
 ]
 
-DIRS += [ 'mozurl', 'rust-helper' ]
+DIRS += [ 'mozurl', 'rust-helper', 'http-sfv' ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -591,16 +591,23 @@ Classes = [
             '@mozilla.org/cookiemanager;1',
         ],
         'interfaces': ['nsICookieManager'],
         'singleton': True,
         'type': 'nsICookieService',
         'constructor': 'mozilla::net::CookieService::GetXPCOMSingleton',
         'headers': ['/netwerk/cookie/CookieService.h'],
     },
+    {
+        'cid': '{e1676f84-e6e5-45d0-a4bf-d9905efc5b2e}',
+        'contract_ids': ['@mozilla.org/http-sfv-service;1'],
+        'singleton': True,
+        'constructor': 'mozilla::net::GetSFVService',
+        'headers': ['mozilla/net/SFVService.h'],
+    },
 ]
 
 if defined('NECKO_WIFI'):
     Classes += [
         {
             'cid': '{3ff8fb9f-ee63-48df-89f0-dace0242fd82}',
             'contract_ids': ['@mozilla.org/wifi/monitor;1'],
             'singleton': True,
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_http_sfv.js
@@ -0,0 +1,590 @@
+"use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { AppConstants } = ChromeUtils.import(
+  "resource://gre/modules/AppConstants.jsm"
+);
+const gService = Cc["@mozilla.org/http-sfv-service;1"].getService(
+  Ci.nsISFVService
+);
+
+add_task(async function test_sfv_bare_item() {
+  // tests bare item
+  let item_int = gService.newInteger(19);
+  Assert.equal(item_int.value, 19, "check bare item value");
+
+  let item_bool = gService.newBool(true);
+  Assert.equal(item_bool.value, true, "check bare item value");
+  item_bool.value = false;
+  Assert.equal(item_bool.value, false, "check bare item value");
+
+  let item_float = gService.newDecimal(145.45);
+  Assert.equal(item_float.value, 145.45);
+
+  let item_str = gService.newString("some_string");
+  Assert.equal(item_str.value, "some_string", "check bare item value");
+
+  let item_byte_seq = gService.newByteSequence("aGVsbG8=");
+  Assert.equal(item_byte_seq.value, "aGVsbG8=", "check bare item value");
+
+  let item_token = gService.newToken("*a");
+  Assert.equal(item_token.value, "*a", "check bare item value");
+});
+
+add_task(async function test_sfv_params() {
+  // test params
+  let params = gService.newParameters();
+  let bool_param = gService.newBool(false);
+  let int_param = gService.newInteger(15);
+  let decimal_param = gService.newDecimal(15.45);
+
+  params.set("bool_param", bool_param);
+  params.set("int_param", int_param);
+  params.set("decimal_param", decimal_param);
+
+  Assert.throws(
+    () => {
+      params.get("some_param");
+    },
+    /NS_ERROR_UNEXPECTED/,
+    "must throw exception as key does not exist in parameters"
+  );
+  Assert.equal(
+    params.get("bool_param").QueryInterface(Ci.nsISFVBool).value,
+    false,
+    "get parameter by key and check its value"
+  );
+  Assert.equal(
+    params.get("int_param").QueryInterface(Ci.nsISFVInteger).value,
+    15,
+    "get parameter by key and check its value"
+  );
+  Assert.equal(
+    params.get("decimal_param").QueryInterface(Ci.nsISFVDecimal).value,
+    15.45,
+    "get parameter by key and check its value"
+  );
+  Assert.deepEqual(
+    params.keys(),
+    ["bool_param", "int_param", "decimal_param"],
+    "check that parameters contain all the expected keys"
+  );
+
+  params.delete("int_param");
+  Assert.deepEqual(
+    params.keys(),
+    ["bool_param", "decimal_param"],
+    "check that parameter has been deleted"
+  );
+
+  Assert.throws(
+    () => {
+      params.delete("some_param");
+    },
+    /NS_ERROR_UNEXPECTED/,
+    "must throw exception upon attempt to delete by non-existing key"
+  );
+});
+
+add_task(async function test_sfv_inner_list() {
+  // create primitives for inner list
+  let item1_params = gService.newParameters();
+  item1_params.set("param_1", gService.newToken("*smth"));
+  let item1 = gService.newItem(gService.newDecimal(172.145865), item1_params);
+
+  let item2_params = gService.newParameters();
+  item2_params.set("param_1", gService.newBool(true));
+  item2_params.set("param_2", gService.newInteger(145454));
+  let item2 = gService.newItem(
+    gService.newByteSequence("weather"),
+    item2_params
+  );
+
+  // create inner list
+  let inner_list_params = gService.newParameters();
+  inner_list_params.set("inner_param", gService.newByteSequence("tests"));
+  let inner_list = gService.newInnerList([item1, item2], inner_list_params);
+
+  // check inner list members & params
+  let inner_list_members = inner_list.QueryInterface(Ci.nsISFVInnerList).items;
+  let inner_list_parameters = inner_list
+    .QueryInterface(Ci.nsISFVInnerList)
+    .params.QueryInterface(Ci.nsISFVParams);
+  Assert.equal(inner_list_members.length, 2, "check inner list length");
+
+  let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
+  Assert.equal(
+    inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
+    172.145865,
+    "check inner list member value"
+  );
+
+  let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
+  Assert.equal(
+    inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
+    "weather",
+    "check inner list member value"
+  );
+
+  Assert.equal(
+    inner_list_parameters.get("inner_param").QueryInterface(Ci.nsISFVByteSeq)
+      .value,
+    "tests",
+    "get inner list parameter by key and check its value"
+  );
+});
+
+add_task(async function test_sfv_item() {
+  // create parameters for item
+  let params = gService.newParameters();
+  let param1 = gService.newBool(false);
+  let param2 = gService.newString("str_value");
+  let param3 = gService.newBool(true);
+  params.set("param_1", param1);
+  params.set("param_2", param2);
+  params.set("param_3", param3);
+
+  // create item field
+  let item = gService.newItem(gService.newToken("*abc"), params);
+
+  Assert.equal(
+    item.value.QueryInterface(Ci.nsISFVToken).value,
+    "*abc",
+    "check items's value"
+  );
+  Assert.equal(
+    item.params.get("param_1").QueryInterface(Ci.nsISFVBool).value,
+    false,
+    "get item parameter by key and check its value"
+  );
+  Assert.equal(
+    item.params.get("param_2").QueryInterface(Ci.nsISFVString).value,
+    "str_value",
+    "get item parameter by key and check its value"
+  );
+  Assert.equal(
+    item.params.get("param_3").QueryInterface(Ci.nsISFVBool).value,
+    true,
+    "get item parameter by key and check its value"
+  );
+
+  // check item field serialization
+  let serialized = item.serialize();
+  Assert.equal(
+    serialized,
+    `*abc;param_1=?0;param_2="str_value";param_3`,
+    "serialized output must match expected one"
+  );
+});
+
+add_task(async function test_sfv_list() {
+  // create primitives for List
+  let item1_params = gService.newParameters();
+  item1_params.set("param_1", gService.newToken("*smth"));
+  let item1 = gService.newItem(gService.newDecimal(145454.14568), item1_params);
+
+  let item2_params = gService.newParameters();
+  item2_params.set("param_1", gService.newBool(true));
+  let item2 = gService.newItem(
+    gService.newByteSequence("weather"),
+    item2_params
+  );
+
+  let inner_list = gService.newInnerList(
+    [item1, item2],
+    gService.newParameters()
+  );
+
+  // create list field
+  let list = gService.newList([item1, inner_list]);
+
+  // check list's members
+  let list_members = list.members;
+  Assert.equal(list_members.length, 2, "check list length");
+
+  // check list's member of item type
+  let member1 = list_members[0].QueryInterface(Ci.nsISFVItem);
+  Assert.equal(
+    member1.value.QueryInterface(Ci.nsISFVDecimal).value,
+    145454.14568,
+    "check list member's value"
+  );
+  let member1_parameters = member1.params;
+  Assert.equal(
+    member1_parameters.get("param_1").QueryInterface(Ci.nsISFVToken).value,
+    "*smth",
+    "get list member's parameter by key and check its value"
+  );
+
+  // check list's member of inner list type
+  let inner_list_members = list_members[1].QueryInterface(Ci.nsISFVInnerList)
+    .items;
+  Assert.equal(inner_list_members.length, 2, "check inner list length");
+
+  let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
+  Assert.equal(
+    inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
+    145454.14568,
+    "check inner list member's value"
+  );
+
+  let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
+  Assert.equal(
+    inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
+    "weather",
+    "check inner list member's value"
+  );
+
+  // check inner list member's params
+  let inner_list_parameters = list_members[1]
+    .QueryInterface(Ci.nsISFVInnerList)
+    .params.QueryInterface(Ci.nsISFVParams);
+
+  // check serialization of list field
+  let expected_serialized =
+    "145454.146;param_1=*smth, (145454.146;param_1=*smth :d2VhdGhlcg==:;param_1)";
+  let actual_serialized = list.serialize();
+  Assert.equal(
+    actual_serialized,
+    expected_serialized,
+    "serialized output must match expected one"
+  );
+});
+
+add_task(async function test_sfv_dictionary() {
+  // create primitives for dictionary field
+
+  // dict member1
+  let params1 = gService.newParameters();
+  params1.set("mp_1", gService.newBool(true));
+  params1.set("mp_2", gService.newDecimal(68.758602));
+  let member1 = gService.newItem(gService.newString("member_1"), params1);
+
+  // dict member2
+  let params2 = gService.newParameters();
+  let inner_item1 = gService.newItem(
+    gService.newString("inner_item_1"),
+    gService.newParameters()
+  );
+  let inner_item2 = gService.newItem(
+    gService.newToken("tok"),
+    gService.newParameters()
+  );
+  let member2 = gService.newInnerList([inner_item1, inner_item2], params2);
+
+  // dict member3
+  let params_3 = gService.newParameters();
+  params_3.set("mp_1", gService.newInteger(6586));
+  let member3 = gService.newItem(gService.newString("member_3"), params_3);
+
+  // create dictionary field
+  let dict = gService.newDictionary();
+  dict.set("key_1", member1);
+  dict.set("key_2", member2);
+  dict.set("key_3", member3);
+
+  // check dictionary keys
+  let expected = ["key_1", "key_2", "key_3"];
+  Assert.deepEqual(
+    expected,
+    dict.keys(),
+    "check dictionary contains all the expected keys"
+  );
+
+  // check dictionary members
+  Assert.throws(
+    () => {
+      dict.get("key_4");
+    },
+    /NS_ERROR_UNEXPECTED/,
+    "must throw exception as key does not exist in dictionary"
+  );
+
+  let dict_member1 = dict.get("key_1").QueryInterface(Ci.nsISFVItem);
+  let dict_member2 = dict.get("key_2").QueryInterface(Ci.nsISFVInnerList);
+  let dict_member3 = dict.get("key_3").QueryInterface(Ci.nsISFVItem);
+
+  // Assert.equal(
+  //   dict_member1.value.QueryInterface(Ci.nsISFVString).value,
+  //   "member_1",
+  //   "check dictionary member's value"
+  // );
+  // Assert.equal(
+  //   dict_member1.params.get("mp_1").QueryInterface(Ci.nsISFVBool).value,
+  //   true,
+  //   "get dictionary member's parameter by key and check its value"
+  // );
+  // Assert.equal(
+  //   dict_member1.params.get("mp_2").QueryInterface(Ci.nsISFVDecimal).value,
+  //   "68.758602",
+  //   "get dictionary member's parameter by key and check its value"
+  // );
+
+  let dict_member2_items = dict_member2.QueryInterface(Ci.nsISFVInnerList)
+    .items;
+  let dict_member2_params = dict_member2
+    .QueryInterface(Ci.nsISFVInnerList)
+    .params.QueryInterface(Ci.nsISFVParams);
+  Assert.equal(
+    dict_member2_items[0]
+      .QueryInterface(Ci.nsISFVItem)
+      .value.QueryInterface(Ci.nsISFVString).value,
+    "inner_item_1",
+    "get dictionary member of inner list type, and check inner list member's value"
+  );
+  Assert.equal(
+    dict_member2_items[1]
+      .QueryInterface(Ci.nsISFVItem)
+      .value.QueryInterface(Ci.nsISFVToken).value,
+    "tok",
+    "get dictionary member of inner list type, and check inner list member's value"
+  );
+  Assert.throws(
+    () => {
+      dict_member2_params.get("some_param");
+    },
+    /NS_ERROR_UNEXPECTED/,
+    "must throw exception as dict member's parameters are empty"
+  );
+
+  Assert.equal(
+    dict_member3.value.QueryInterface(Ci.nsISFVString).value,
+    "member_3",
+    "check dictionary member's value"
+  );
+  Assert.equal(
+    dict_member3.params.get("mp_1").QueryInterface(Ci.nsISFVInteger).value,
+    6586,
+    "get dictionary member's parameter by key and check its value"
+  );
+
+  // check serialization of Dictionary field
+  let expected_serialized = `key_1="member_1";mp_1;mp_2=68.759, key_2=("inner_item_1" tok), key_3="member_3";mp_1=6586`;
+  let actual_serialized = dict.serialize();
+  Assert.equal(
+    actual_serialized,
+    expected_serialized,
+    "serialized output must match expected one"
+  );
+
+  // check deleting dict member
+  dict.delete("key_2");
+  Assert.deepEqual(
+    dict.keys(),
+    ["key_1", "key_3"],
+    "check that dictionary member has been deleted"
+  );
+
+  Assert.throws(
+    () => {
+      dict.delete("some_key");
+    },
+    /NS_ERROR_UNEXPECTED/,
+    "must throw exception upon attempt to delete by non-existing key"
+  );
+});
+
+add_task(async function test_sfv_item_parsing() {
+  Assert.ok(gService.parseItem(`"str"`), "input must be parsed into Item");
+  Assert.ok(gService.parseItem("12.35;a "), "input must be parsed into Item");
+  Assert.ok(gService.parseItem("12.35;  a "), "input must be parsed into Item");
+  Assert.ok(gService.parseItem("12.35    "), "input must be parsed into Item");
+
+  Assert.throws(
+    () => {
+      gService.parseItem("12.35;\ta ");
+    },
+    /NS_ERROR_FAILURE/,
+    "item parsing must fail: invalid parameters delimiter"
+  );
+
+  Assert.throws(
+    () => {
+      gService.parseItem("125666.3565648855455");
+    },
+    /NS_ERROR_FAILURE/,
+    "item parsing must fail: decimal too long"
+  );
+});
+
+add_task(async function test_sfv_list_parsing() {
+  Assert.ok(
+    gService.parseList(
+      "(?1;param_1=*smth   :d2VhdGhlcg==:;param_1;param_2=145454);inner_param=:d2VpcmR0ZXN0cw==:"
+    ),
+    "input must be parsed into List"
+  );
+  Assert.ok("a, (b c)", "input must be parsed into List");
+
+  Assert.throws(() => {
+    gService.parseList("?tok", "list parsing must fail");
+  }, /NS_ERROR_FAILURE/);
+
+  Assert.throws(() => {
+    gService.parseList(
+      "a, (b, c)",
+      "list parsing must fail: invalid delimiter within inner list"
+    );
+  }, /NS_ERROR_FAILURE/);
+
+  Assert.throws(
+    () => {
+      gService.parseList("a, b c");
+    },
+    /NS_ERROR_FAILURE/,
+    "list parsing must fail: invalid delimiter"
+  );
+});
+
+add_task(async function test_sfv_dict_parsing() {
+  Assert.ok(
+    gService.parseDictionary(`abc=123;a=1;b=2, def=456, ghi=789;q=9;r="+w"`),
+    "input must be parsed into Dictionary"
+  );
+  Assert.ok(
+    gService.parseDictionary("a=1\t,\t\t\t   c=*"),
+    "input must be parsed into Dictionary"
+  );
+  Assert.ok(
+    gService.parseDictionary("a=1\t,\tc=*    \t\t"),
+    "input must be parsed into Dictionary"
+  );
+
+  Assert.throws(
+    () => {
+      gService.parseDictionary("a=1\t,\tc=*,");
+    },
+    /NS_ERROR_FAILURE/,
+    "dictionary parsing must fail: trailing comma"
+  );
+
+  Assert.throws(
+    () => {
+      gService.parseDictionary("a=1 c=*");
+    },
+    /NS_ERROR_FAILURE/,
+    "dictionary parsing must fail: invalid delimiter"
+  );
+
+  Assert.throws(
+    () => {
+      gService.parseDictionary("INVALID_key=1, c=*");
+    },
+    /NS_ERROR_FAILURE/,
+    "dictionary parsing must fail: invalid key format, can't be in uppercase"
+  );
+});
+
+add_task(async function test_sfv_list_parse_serialize() {
+  let list_field = gService.parseList("1  ,  42, (42 43)");
+  Assert.equal(
+    list_field.serialize(),
+    "1, 42, (42 43)",
+    "serialized output must match expected one"
+  );
+
+  // create new inner list with parameters
+  let inner_list_params = gService.newParameters();
+  inner_list_params.set("key1", gService.newString("value1"));
+  inner_list_params.set("key2", gService.newBool(true));
+  inner_list_params.set("key3", gService.newBool(false));
+  let inner_list_items = [
+    gService.newItem(
+      gService.newDecimal(-1865.75653),
+      gService.newParameters()
+    ),
+    gService.newItem(gService.newToken("token"), gService.newParameters()),
+    gService.newItem(gService.newString(`no"yes`), gService.newParameters()),
+  ];
+  let new_list_member = gService.newInnerList(
+    inner_list_items,
+    inner_list_params
+  );
+
+  // set one of list members to inner list and check it's serialized as expected
+  let members = list_field.members;
+  members[1] = new_list_member;
+  list_field.members = members;
+  Assert.equal(
+    list_field.serialize(),
+    `1, (-1865.757 token "no\\"yes");key1="value1";key2;key3=?0, (42 43)`,
+    "update list member and check list is serialized as expected"
+  );
+});
+
+add_task(async function test_sfv_dict_parse_serialize() {
+  let dict_field = gService.parseDictionary(
+    "a=1,     b; foo=*, \tc=3, \t     \tabc=123;a=1;b=2\t"
+  );
+  Assert.equal(
+    dict_field.serialize(),
+    "a=1, b;foo=*, c=3, abc=123;a=1;b=2",
+    "serialized output must match expected one"
+  );
+
+  // set new value for existing dict's key
+  dict_field.set(
+    "a",
+    gService.newItem(gService.newInteger(165), gService.newParameters())
+  );
+
+  // add new member to dict
+  dict_field.set(
+    "key",
+    gService.newItem(gService.newDecimal(45.0), gService.newParameters())
+  );
+
+  // check dict is serialized properly after the above changes
+  Assert.equal(
+    dict_field.serialize(),
+    "a=165, b;foo=*, c=3, abc=123;a=1;b=2, key=45.0",
+    "update dictionary members and dictionary list is serialized as expected"
+  );
+});
+
+add_task(async function test_sfv_list_parse_more() {
+  // check parsing of multiline header of List type
+  let list_field = gService.parseList("(12 abc), 12.456\t\t   ");
+  list_field.parseMore("11, 15, tok");
+  Assert.equal(
+    list_field.serialize(),
+    "(12 abc), 12.456, 11, 15, tok",
+    "multi-line field value parsed and serialized successfully"
+  );
+
+  // should fail parsing one more line
+  Assert.throws(
+    () => {
+      list_field.parseMore("(tk\t1)");
+    },
+    /NS_ERROR_FAILURE/,
+    "line parsing must fail: invalid delimiter in inner list"
+  );
+  Assert.equal(
+    list_field.serialize(),
+    "(12 abc), 12.456, 11, 15, tok",
+    "parsed value must not change if parsing one more line of header fails"
+  );
+});
+
+add_task(async function test_sfv_dict_parse_more() {
+  // check parsing of multiline header of Dictionary type
+  let dict_field = gService.parseDictionary("");
+  dict_field.parseMore("key2=?0, key3=?1, key4=itm");
+  dict_field.parseMore("key1,    key5=11, key4=45");
+
+  Assert.equal(
+    dict_field.serialize(),
+    "key2=?0, key3, key4=45, key1, key5=11",
+    "multi-line field value parsed and serialized successfully"
+  );
+
+  // should fail parsing one more line
+  Assert.throws(
+    () => {
+      dict_field.parseMore("c=12, _k=13");
+    },
+    /NS_ERROR_FAILURE/,
+    "line parsing must fail: invalid key format"
+  );
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -438,9 +438,10 @@ skip-if = asan || tsan || os == 'win' ||
 [test_no_cookies_after_last_pb_exit.js]
 [test_trr_httpssvc.js]
 skip-if = os == "android"
 [test_trr_case_sensitivity.js]
 skip-if = os == "android"
 [test_trr_proxy.js]
 [test_trr_cname_chain.js]
 skip-if = os == "android"
+[test_http_sfv.js]
 [test_blob_channelname.js]
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -45,17 +45,17 @@ audio_thread_priority = "0.23.4"
 mdns_service = { path="../../../../media/mtransport/mdns_service", optional = true }
 neqo_glue = { path = "../../../../netwerk/socket/neqo_glue" }
 rlbox_lucet_sandbox = { version = "0.1.0", optional = true }
 wgpu_bindings = { path = "../../../../gfx/wgpu_bindings", optional = true }
 mapped_hyph = { git = "https://github.com/jfkthame/mapped_hyph.git", tag = "v0.3.0" }
 remote = { path = "../../../../remote", optional = true }
 fog_control = { path = "../../../components/glean", optional = true }
 app_services_logger = { path = "../../../../services/common/app_services_logger" }
-
+http_sfv = { path = "../../../../netwerk/base/http-sfv" }
 unic-langid = { version = "0.8", features = ["likelysubtags"] }
 unic-langid-ffi = { path = "../../../../intl/locale/rust/unic-langid-ffi" }
 fluent-langneg = { version = "0.12.1", features = ["cldr"] }
 fluent-langneg-ffi = { path = "../../../../intl/locale/rust/fluent-langneg-ffi" }
 
 # Note: `modern_sqlite` means rusqlite's bindings file be for a sqlite with
 # version less than or equal to what we link to. This isn't a problem because we
 # tend to keep this up to date, but it needs to be taken into consideration when
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -26,16 +26,17 @@ extern crate cubeb_coreaudio;
 #[cfg(feature = "cubeb_pulse_rust")]
 extern crate cubeb_pulse;
 extern crate encoding_glue;
 #[cfg(feature = "rust_fxa_client")]
 extern crate firefox_accounts_bridge;
 #[cfg(feature = "glean")]
 extern crate fog_control;
 extern crate gkrust_utils;
+extern crate http_sfv;
 extern crate jsrust_shared;
 extern crate kvstore;
 extern crate mapped_hyph;
 extern crate mozurl;
 extern crate mp4parse_capi;
 extern crate netwerk_helper;
 extern crate nserror;
 extern crate nsstring;