Snap for 10453563 from a3f1a1d4127619d814963ae5848840850318e401 to mainline-uwb-release

Change-Id: I9d37046524a4e094d2d52370d46b1c8e5f7eab56
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index fb5f61f..5bf7f75 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
 {
   "git": {
-    "sha1": "6c22912c313064a8c5a6fa043882c9ad55dba162"
-  }
-}
+    "sha1": "359bc90a4f07224f79cc79c45dc873d44bcd6f14"
+  },
+  "path_in_vcs": "url"
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 82f0f0b..ac8429c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,19 +42,21 @@
     host_supported: true,
     crate_name: "url",
     cargo_env_compat: true,
-    cargo_pkg_version: "2.2.2",
+    cargo_pkg_version: "2.3.1",
     srcs: ["src/lib.rs"],
     edition: "2018",
+    features: ["default"],
     rustlibs: [
         "libform_urlencoded",
         "libidna",
-        "libmatches",
         "libpercent_encoding",
     ],
     apex_available: [
         "//apex_available:platform",
         "com.android.resolv",
     ],
+    product_available: true,
+    vendor_available: true,
     min_sdk_version: "29",
 }
 
@@ -62,15 +64,15 @@
     name: "url_test_defaults",
     crate_name: "url",
     cargo_env_compat: true,
-    cargo_pkg_version: "2.2.2",
+    cargo_pkg_version: "2.3.1",
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
+    features: ["default"],
     rustlibs: [
         "libbencher",
         "libform_urlencoded",
         "libidna",
-        "libmatches",
         "libpercent_encoding",
         "libserde_json",
         "liburl",
diff --git a/Cargo.toml b/Cargo.toml
index 108b149..4363ae5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,52 +3,81 @@
 # When uploading crates to the registry Cargo will automatically
 # "normalize" Cargo.toml files for maximal compatibility
 # with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies
+# to registry (e.g., crates.io) dependencies.
 #
-# If you believe there's an error in this file please file an
-# issue against the rust-lang/cargo repository. If you're
-# editing this file be aware that the upstream Cargo.toml
-# will likely look very different (and much more reasonable)
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
 
 [package]
 edition = "2018"
+rust-version = "1.51"
 name = "url"
-version = "2.2.2"
+version = "2.3.1"
 authors = ["The rust-url developers"]
-include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"]
+include = [
+    "src/**/*",
+    "LICENSE-*",
+    "README.md",
+    "tests/**",
+]
 description = "URL library for Rust, based on the WHATWG URL Standard"
 documentation = "https://docs.rs/url"
-readme = "../README.md"
-keywords = ["url", "parser"]
-categories = ["parser-implementations", "web-programming", "encoding"]
-license = "MIT/Apache-2.0"
+readme = "README.md"
+keywords = [
+    "url",
+    "parser",
+]
+categories = [
+    "parser-implementations",
+    "web-programming",
+    "encoding",
+]
+license = "MIT OR Apache-2.0"
 repository = "https://github.com/servo/rust-url"
 
+[[test]]
+name = "debugger_visualizer"
+path = "tests/debugger_visualizer.rs"
+test = false
+required-features = ["debugger_visualizer"]
+
 [[bench]]
 name = "parse_url"
 path = "benches/parse_url.rs"
 harness = false
+
 [dependencies.form_urlencoded]
-version = "1.0.0"
+version = "1.1.0"
 
 [dependencies.idna]
-version = "0.2.0"
-
-[dependencies.matches]
-version = "0.1"
+version = "0.3.0"
 
 [dependencies.percent-encoding]
-version = "2.1.0"
+version = "2.2.0"
 
 [dependencies.serde]
 version = "1.0"
 features = ["derive"]
 optional = true
+
 [dev-dependencies.bencher]
 version = "0.1"
 
+[dev-dependencies.debugger_test]
+version = "0.1"
+
+[dev-dependencies.debugger_test_parser]
+version = "0.1"
+
 [dev-dependencies.serde_json]
 version = "1.0"
+
+[features]
+debugger_visualizer = []
+default = []
+expose_internals = []
+
 [badges.appveyor]
 repository = "Manishearth/rust-url"
 
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index e384659..39b90fa 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -2,7 +2,7 @@
 
 name = "url"
 # When updating version, also modify html_root_url in the lib.rs
-version = "2.2.2"
+version = "2.3.1"
 authors = ["The rust-url developers"]
 
 description = "URL library for Rust, based on the WHATWG URL Standard"
@@ -11,9 +11,10 @@
 readme = "../README.md"
 keywords = ["url", "parser"]
 categories = ["parser-implementations", "web-programming", "encoding"]
-license = "MIT/Apache-2.0"
+license = "MIT OR Apache-2.0"
 include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"]
 edition = "2018"
+rust-version = "1.51"
 
 [badges]
 travis-ci = { repository = "servo/rust-url" }
@@ -22,15 +23,31 @@
 [dev-dependencies]
 serde_json = "1.0"
 bencher = "0.1"
+# To test debugger visualizers defined for the url crate such as url.natvis
+debugger_test = "0.1"
+debugger_test_parser = "0.1"
 
 [dependencies]
-form_urlencoded = { version = "1.0.0", path = "../form_urlencoded" }
-idna = { version = "0.2.0", path = "../idna" }
-matches = "0.1"
-percent-encoding = { version = "2.1.0", path = "../percent_encoding" }
+form_urlencoded = { version = "1.1.0", path = "../form_urlencoded" }
+idna = { version = "0.3.0", path = "../idna" }
+percent-encoding = { version = "2.2.0", path = "../percent_encoding" }
 serde = {version = "1.0", optional = true, features = ["derive"]}
 
+[features]
+default = []
+# UNSTABLE FEATURES (requires Rust nightly)
+# Enable to use the #[debugger_visualizer] attribute.
+debugger_visualizer = []
+# Expose internal offsets of the URL.
+expose_internals = []
+
 [[bench]]
 name = "parse_url"
 path = "benches/parse_url.rs"
 harness = false
+
+[[test]]
+name = "debugger_visualizer"
+path = "tests/debugger_visualizer.rs"
+required-features = ["debugger_visualizer"]
+test = false
diff --git a/LICENSE-MIT b/LICENSE-MIT
index 24de6b4..51d5dc7 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2016 The rust-url developers
+Copyright (c) 2013-2022 The rust-url developers
 
 Permission is hereby granted, free of charge, to any
 person obtaining a copy of this software and associated
diff --git a/METADATA b/METADATA
index 742a22a..4f7711e 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/url
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
 name: "url"
 description: "URL library for Rust, based on the WHATWG URL Standard"
 third_party {
@@ -7,13 +11,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/url/url-2.2.2.crate"
+    value: "https://static.crates.io/crates/url/url-2.3.1.crate"
   }
-  version: "2.2.2"
+  version: "2.3.1"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 5
-    day: 7
+    year: 2022
+    month: 12
+    day: 19
   }
 }
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab31bff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+rust-url
+========
+
+[![Build status](https://github.com/servo/rust-url/workflows/CI/badge.svg)](https://github.com/servo/rust-url/actions?query=workflow%3ACI)
+[![Coverage](https://codecov.io/gh/servo/rust-url/branch/master/graph/badge.svg)](https://codecov.io/gh/servo/rust-url)
+[![Chat](https://img.shields.io/badge/chat-%23rust--url:mozilla.org-%2346BC99?logo=Matrix)](https://matrix.to/#/#rust-url:mozilla.org)
+[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT)
+[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE-APACHE)
+
+URL library for Rust, based on the [URL Standard](https://url.spec.whatwg.org/).
+
+[Documentation](https://docs.rs/url/)
+
+Please see [UPGRADING.md](https://github.com/servo/rust-url/blob/master/UPGRADING.md) if you are upgrading from a previous version.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index cc34a06..dfa2464 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,13 +3,13 @@
   "imports": [
     {
       "path": "external/rust/crates/quiche"
+    },
+    {
+      "path": "packages/modules/DnsResolver"
     }
   ],
   "presubmit": [
     {
-      "name": "doh_unit_test"
-    },
-    {
       "name": "url_test_tests_data"
     },
     {
@@ -18,9 +18,6 @@
   ],
   "presubmit-rust": [
     {
-      "name": "doh_unit_test"
-    },
-    {
       "name": "url_test_tests_data"
     },
     {
diff --git a/cargo2android.json b/cargo2android.json
index 911c36f..e1e5077 100644
--- a/cargo2android.json
+++ b/cargo2android.json
@@ -1,4 +1,11 @@
 {
+  "dependency-blocklist": [
+    "debugger_test",
+    "debugger_test_parser"
+  ],
+  "test-blocklist": [
+    "tests/debugger_visualizer.rs"
+  ],
   "apex-available": [
     "//apex_available:platform",
     "com.android.resolv"
@@ -8,4 +15,4 @@
   "min-sdk-version": "29",
   "run": true,
   "tests": true
-}
\ No newline at end of file
+}
diff --git a/src/host.rs b/src/host.rs
index 9537436..f1921c6 100644
--- a/src/host.rs
+++ b/src/host.rs
@@ -82,7 +82,9 @@
             return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6);
         }
         let domain = percent_decode(input.as_bytes()).decode_utf8_lossy();
-        let domain = idna::domain_to_ascii(&domain)?;
+
+        let domain = Self::domain_to_ascii(&domain)?;
+
         if domain.is_empty() {
             return Err(ParseError::EmptyHost);
         }
@@ -90,9 +92,7 @@
         let is_invalid_domain_char = |c| {
             matches!(
                 c,
-                '\0' | '\t'
-                    | '\n'
-                    | '\r'
+                '\0'..='\u{001F}'
                     | ' '
                     | '#'
                     | '%'
@@ -106,12 +106,15 @@
                     | '\\'
                     | ']'
                     | '^'
+                    | '\u{007F}'
+                    | '|'
             )
         };
 
         if domain.find(is_invalid_domain_char).is_some() {
             Err(ParseError::InvalidDomainCharacter)
-        } else if let Some(address) = parse_ipv4addr(&domain)? {
+        } else if ends_in_a_number(&domain) {
+            let address = parse_ipv4addr(&domain)?;
             Ok(Host::Ipv4(address))
         } else {
             Ok(Host::Domain(domain))
@@ -145,6 +148,7 @@
                     | '\\'
                     | ']'
                     | '^'
+                    | '|'
             )
         };
 
@@ -156,6 +160,11 @@
             ))
         }
     }
+
+    /// convert domain with idna
+    fn domain_to_ascii(domain: &str) -> Result<String, ParseError> {
+        idna::domain_to_ascii(domain).map_err(Into::into)
+    }
 }
 
 impl<S: AsRef<str>> fmt::Display for Host<S> {
@@ -247,8 +256,33 @@
     }
 }
 
+/// <https://url.spec.whatwg.org/#ends-in-a-number-checker>
+fn ends_in_a_number(input: &str) -> bool {
+    let mut parts = input.rsplit('.');
+    let last = parts.next().unwrap();
+    let last = if last.is_empty() {
+        if let Some(last) = parts.next() {
+            last
+        } else {
+            return false;
+        }
+    } else {
+        last
+    };
+    if !last.is_empty() && last.chars().all(|c| ('0'..='9').contains(&c)) {
+        return true;
+    }
+
+    parse_ipv4number(last).is_ok()
+}
+
 /// <https://url.spec.whatwg.org/#ipv4-number-parser>
+/// Ok(None) means the input is a valid number, but it overflows a `u32`.
 fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
+    if input.is_empty() {
+        return Err(());
+    }
+
     let mut r = 10;
     if input.starts_with("0x") || input.starts_with("0X") {
         input = &input[2..];
@@ -258,10 +292,10 @@
         r = 8;
     }
 
-    // At the moment we can't know the reason why from_str_radix fails
-    // https://github.com/rust-lang/rust/issues/22639
-    // So instead we check if the input looks like a real number and only return
-    // an error when it's an overflow.
+    if input.is_empty() {
+        return Ok(Some(0));
+    }
+
     let valid_number = match r {
         8 => input.chars().all(|c| ('0'..='7').contains(&c)),
         10 => input.chars().all(|c| ('0'..='9').contains(&c)),
@@ -270,50 +304,34 @@
         }),
         _ => false,
     };
-
     if !valid_number {
-        return Ok(None);
+        return Err(());
     }
 
-    if input.is_empty() {
-        return Ok(Some(0));
-    }
-    if input.starts_with('+') {
-        return Ok(None);
-    }
     match u32::from_str_radix(input, r) {
-        Ok(number) => Ok(Some(number)),
-        Err(_) => Err(()),
+        Ok(num) => Ok(Some(num)),
+        Err(_) => Ok(None), // The only possible error kind here is an integer overflow.
+                            // The validity of the chars in the input is checked above.
     }
 }
 
 /// <https://url.spec.whatwg.org/#concept-ipv4-parser>
-fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
-    if input.is_empty() {
-        return Ok(None);
-    }
+fn parse_ipv4addr(input: &str) -> ParseResult<Ipv4Addr> {
     let mut parts: Vec<&str> = input.split('.').collect();
     if parts.last() == Some(&"") {
         parts.pop();
     }
     if parts.len() > 4 {
-        return Ok(None);
+        return Err(ParseError::InvalidIpv4Address);
     }
     let mut numbers: Vec<u32> = Vec::new();
-    let mut overflow = false;
     for part in parts {
-        if part.is_empty() {
-            return Ok(None);
-        }
         match parse_ipv4number(part) {
             Ok(Some(n)) => numbers.push(n),
-            Ok(None) => return Ok(None),
-            Err(()) => overflow = true,
+            Ok(None) => return Err(ParseError::InvalidIpv4Address), // u32 overflow
+            Err(()) => return Err(ParseError::InvalidIpv4Address),
         };
     }
-    if overflow {
-        return Err(ParseError::InvalidIpv4Address);
-    }
     let mut ipv4 = numbers.pop().expect("a non-empty list of numbers");
     // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
     if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
@@ -325,7 +343,7 @@
     for (counter, n) in numbers.iter().enumerate() {
         ipv4 += n << (8 * (3 - counter as u32))
     }
-    Ok(Some(Ipv4Addr::from(ipv4)))
+    Ok(Ipv4Addr::from(ipv4))
 }
 
 /// <https://url.spec.whatwg.org/#concept-ipv6-parser>
diff --git a/src/lib.rs b/src/lib.rs
index 42793cf..6dc09d1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -118,12 +118,16 @@
 ```toml
 url = { version = "2", features = ["serde"] }
 ```
+
 */
 
-#![doc(html_root_url = "https://docs.rs/url/2.2.2")]
+#![doc(html_root_url = "https://docs.rs/url/2.3.1")]
+#![cfg_attr(
+    feature = "debugger_visualizer",
+    feature(debugger_visualizer),
+    debugger_visualizer(natvis_file = "../../debug_metadata/url.natvis")
+)]
 
-#[macro_use]
-extern crate matches;
 pub use form_urlencoded;
 
 #[cfg(feature = "serde")]
@@ -460,7 +464,7 @@
         }
 
         // Add the filename if they are not the same
-        if base_filename != url_filename {
+        if !relative.is_empty() || base_filename != url_filename {
             // If the URIs filename is empty this means that it was a directory
             // so we'll have to append a '/'.
             //
@@ -1234,14 +1238,9 @@
     /// # }
     /// # run().unwrap();
     /// ```
-    #[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36
     pub fn path_segments(&self) -> Option<str::Split<'_, char>> {
         let path = self.path();
-        if path.starts_with('/') {
-            Some(path[1..].split('/'))
-        } else {
-            None
-        }
+        path.strip_prefix('/').map(|remainder| remainder.split('/'))
     }
 
     /// Return this URL’s query string, if any, as a percent-encoded ASCII string.
@@ -1304,7 +1303,7 @@
     /// # Ok(())
     /// # }
     /// # run().unwrap();
-    ///
+    /// ```
 
     #[inline]
     pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
@@ -1351,7 +1350,7 @@
     }
 
     fn mutate<F: FnOnce(&mut Parser<'_>) -> R, R>(&mut self, f: F) -> R {
-        let mut parser = Parser::for_setter(mem::replace(&mut self.serialization, String::new()));
+        let mut parser = Parser::for_setter(mem::take(&mut self.serialization));
         let result = f(&mut parser);
         self.serialization = parser.serialization;
         result
@@ -1541,6 +1540,19 @@
     /// url.set_path("data/report.csv");
     /// assert_eq!(url.as_str(), "https://example.com/data/report.csv");
     /// assert_eq!(url.path(), "/data/report.csv");
+    ///
+    /// // `set_path` percent-encodes the given string if it's not already percent-encoded.
+    /// let mut url = Url::parse("https://example.com")?;
+    /// url.set_path("api/some comments");
+    /// assert_eq!(url.as_str(), "https://example.com/api/some%20comments");
+    /// assert_eq!(url.path(), "/api/some%20comments");
+    ///
+    /// // `set_path` will not double percent-encode the string if it's already percent-encoded.
+    /// let mut url = Url::parse("https://example.com")?;
+    /// url.set_path("api/some%20comments");
+    /// assert_eq!(url.as_str(), "https://example.com/api/some%20comments");
+    /// assert_eq!(url.path(), "/api/some%20comments");
+    ///
     /// # Ok(())
     /// # }
     /// # run().unwrap();
@@ -1792,8 +1804,10 @@
             return Err(ParseError::SetHostOnCannotBeABaseUrl);
         }
 
+        let scheme_type = SchemeType::from(self.scheme());
+
         if let Some(host) = host {
-            if host.is_empty() && SchemeType::from(self.scheme()).is_special() {
+            if host.is_empty() && scheme_type.is_special() && !scheme_type.is_file() {
                 return Err(ParseError::EmptyHost);
             }
             let mut host_substr = host;
@@ -1817,15 +1831,20 @@
                 self.set_host_internal(Host::parse_opaque(host_substr)?, None);
             }
         } else if self.has_host() {
-            let scheme_type = SchemeType::from(self.scheme());
-            if scheme_type.is_special() {
+            if scheme_type.is_special() && !scheme_type.is_file() {
                 return Err(ParseError::EmptyHost);
             } else if self.serialization.len() == self.path_start as usize {
                 self.serialization.push('/');
             }
             debug_assert!(self.byte_at(self.scheme_end) == b':');
             debug_assert!(self.byte_at(self.path_start) == b'/');
-            let new_path_start = self.scheme_end + 1;
+
+            let new_path_start = if scheme_type.is_file() {
+                self.scheme_end + 3
+            } else {
+                self.scheme_end + 1
+            };
+
             self.serialization
                 .drain(new_path_start as usize..self.path_start as usize);
             let offset = self.path_start - new_path_start;
@@ -2127,7 +2146,7 @@
     ///
     /// # Examples
     ///
-    /// Change the URL’s scheme from `https` to `foo`:
+    /// Change the URL’s scheme from `https` to `http`:
     ///
     /// ```
     /// use url::Url;
@@ -2298,7 +2317,7 @@
     /// # run().unwrap();
     /// # }
     /// ```
-    #[cfg(any(unix, windows, target_os = "redox"))]
+    #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
     #[allow(clippy::result_unit_err)]
     pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
         let mut serialization = "file://".to_owned();
@@ -2335,7 +2354,7 @@
     ///
     /// Note that `std::path` does not consider trailing slashes significant
     /// and usually does not include them (e.g. in `Path::parent()`).
-    #[cfg(any(unix, windows, target_os = "redox"))]
+    #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
     #[allow(clippy::result_unit_err)]
     pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
         let mut url = Url::from_file_path(path)?;
@@ -2452,7 +2471,7 @@
     /// (That is, if the percent-decoded path contains a NUL byte or,
     /// for a Windows path, is not UTF-8.)
     #[inline]
-    #[cfg(any(unix, windows, target_os = "redox"))]
+    #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
     #[allow(clippy::result_unit_err)]
     pub fn to_file_path(&self) -> Result<PathBuf, ()> {
         if let Some(segments) = self.path_segments() {
@@ -2511,7 +2530,7 @@
     }
 }
 
-/// String converstion.
+/// String conversion.
 impl From<Url> for String {
     fn from(value: Url) -> String {
         value.serialization
@@ -2656,12 +2675,15 @@
     }
 }
 
-#[cfg(any(unix, target_os = "redox"))]
+#[cfg(any(unix, target_os = "redox", target_os = "wasi"))]
 fn path_to_file_url_segments(
     path: &Path,
     serialization: &mut String,
 ) -> Result<(u32, HostInternal), ()> {
+    #[cfg(any(unix, target_os = "redox"))]
     use std::os::unix::prelude::OsStrExt;
+    #[cfg(target_os = "wasi")]
+    use std::os::wasi::prelude::OsStrExt;
     if !path.is_absolute() {
         return Err(());
     }
@@ -2706,6 +2728,7 @@
     let host_start = serialization.len() + 1;
     let host_end;
     let host_internal;
+
     match components.next() {
         Some(Component::Prefix(ref p)) => match p.kind() {
             Prefix::Disk(letter) | Prefix::VerbatimDisk(letter) => {
@@ -2726,7 +2749,6 @@
             }
             _ => return Err(()),
         },
-
         _ => return Err(()),
     }
 
@@ -2735,12 +2757,15 @@
         if component == Component::RootDir {
             continue;
         }
+
         path_only_has_prefix = false;
         // FIXME: somehow work with non-unicode?
         let component = component.as_os_str().to_str().ok_or(())?;
+
         serialization.push('/');
         serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT));
     }
+
     // A windows drive letter must end with a slash.
     if serialization.len() > host_start
         && parser::is_windows_drive_letter(&serialization[host_start..])
@@ -2748,16 +2773,20 @@
     {
         serialization.push('/');
     }
+
     Ok((host_end, host_internal))
 }
 
-#[cfg(any(unix, target_os = "redox"))]
+#[cfg(any(unix, target_os = "redox", target_os = "wasi"))]
 fn file_url_segments_to_pathbuf(
     host: Option<&str>,
     segments: str::Split<'_, char>,
 ) -> Result<PathBuf, ()> {
     use std::ffi::OsStr;
+    #[cfg(any(unix, target_os = "redox"))]
     use std::os::unix::prelude::OsStrExt;
+    #[cfg(target_os = "wasi")]
+    use std::os::wasi::prelude::OsStrExt;
 
     if host.is_some() {
         return Err(());
@@ -2768,10 +2797,12 @@
     } else {
         Vec::new()
     };
+
     for segment in segments {
         bytes.push(b'/');
         bytes.extend(percent_decode(segment.as_bytes()));
     }
+
     // A windows drive letter must end with a slash.
     if bytes.len() > 2
         && matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z')
@@ -2779,12 +2810,15 @@
     {
         bytes.push(b'/');
     }
+
     let os_str = OsStr::from_bytes(&bytes);
     let path = PathBuf::from(os_str);
+
     debug_assert!(
         path.is_absolute(),
         "to_file_path() failed to produce an absolute Path"
     );
+
     Ok(path)
 }
 
diff --git a/src/origin.rs b/src/origin.rs
index be2d948..81193f5 100644
--- a/src/origin.rs
+++ b/src/origin.rs
@@ -9,7 +9,6 @@
 use crate::host::Host;
 use crate::parser::default_port;
 use crate::Url;
-use idna::domain_to_unicode;
 use std::sync::atomic::{AtomicUsize, Ordering};
 
 pub fn url_origin(url: &Url) -> Origin {
@@ -93,7 +92,7 @@
             Origin::Tuple(ref scheme, ref host, port) => {
                 let host = match *host {
                     Host::Domain(ref domain) => {
-                        let (domain, _errors) = domain_to_unicode(domain);
+                        let (domain, _errors) = idna::domain_to_unicode(domain);
                         Host::Domain(domain)
                     }
                     _ => host.clone(),
diff --git a/src/parser.rs b/src/parser.rs
index 57be110..f5438c5 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -52,15 +52,12 @@
         ///
         /// This may be extended in the future so exhaustive matching is
         /// discouraged with an unused variant.
-        #[allow(clippy::manual_non_exhaustive)] // introduced in 1.40, MSRV is 1.36
         #[derive(PartialEq, Eq, Clone, Copy, Debug)]
+        #[non_exhaustive]
         pub enum ParseError {
             $(
                 $name,
             )+
-            /// Unused variant enable non-exhaustive matching
-            #[doc(hidden)]
-            __FutureProof,
         }
 
         impl fmt::Display for ParseError {
@@ -69,9 +66,6 @@
                     $(
                         ParseError::$name => fmt.write_str($description),
                     )+
-                    ParseError::__FutureProof => {
-                        unreachable!("Don't abuse the FutureProof!");
-                    }
                 }
             }
         }
@@ -105,15 +99,12 @@
         ///
         /// This may be extended in the future so exhaustive matching is
         /// discouraged with an unused variant.
-        #[allow(clippy::manual_non_exhaustive)] // introduced in 1.40, MSRV is 1.36
         #[derive(PartialEq, Eq, Clone, Copy, Debug)]
+        #[non_exhaustive]
         pub enum SyntaxViolation {
             $(
                 $name,
             )+
-            /// Unused variant enable non-exhaustive matching
-            #[doc(hidden)]
-            __FutureProof,
         }
 
         impl SyntaxViolation {
@@ -122,9 +113,6 @@
                     $(
                         SyntaxViolation::$name => $description,
                     )+
-                    SyntaxViolation::__FutureProof => {
-                        unreachable!("Don't abuse the FutureProof!");
-                    }
                 }
             }
         }
@@ -154,7 +142,7 @@
     }
 }
 
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Copy, Clone, PartialEq, Eq)]
 pub enum SchemeType {
     File,
     SpecialNotFile,
@@ -1227,13 +1215,11 @@
                     }
                 }
             }
-            // Going from &str to String to &str to please the 1.33.0 borrow checker
-            let before_slash_string = if ends_with_slash {
-                self.serialization[segment_start..self.serialization.len() - 1].to_owned()
+            let segment_before_slash = if ends_with_slash {
+                &self.serialization[segment_start..self.serialization.len() - 1]
             } else {
-                self.serialization[segment_start..self.serialization.len()].to_owned()
+                &self.serialization[segment_start..self.serialization.len()]
             };
-            let segment_before_slash: &str = &before_slash_string;
             match segment_before_slash {
                 // If buffer is a double-dot path segment, shorten url’s path,
                 ".." | "%2e%2e" | "%2e%2E" | "%2E%2e" | "%2E%2E" | "%2e." | "%2E." | ".%2e"
@@ -1292,7 +1278,7 @@
             //FIXME: log violation
             let path = self.serialization.split_off(path_start);
             self.serialization.push('/');
-            self.serialization.push_str(&path.trim_start_matches('/'));
+            self.serialization.push_str(path.trim_start_matches('/'));
         }
 
         input
@@ -1423,7 +1409,8 @@
         scheme_end: u32,
         mut input: Input<'i>,
     ) -> Option<Input<'i>> {
-        let mut query = String::new(); // FIXME: use a streaming decoder instead
+        let len = input.chars.as_str().len();
+        let mut query = String::with_capacity(len); // FIXME: use a streaming decoder instead
         let mut remaining = None;
         while let Some(c) = input.next() {
             if c == '#' && self.context == Context::UrlParser {
@@ -1563,17 +1550,17 @@
     is_windows_drive_letter(segment) && segment.as_bytes()[1] == b':'
 }
 
-/// Wether the scheme is file:, the path has a single segment, and that segment
+/// Whether the scheme is file:, the path has a single segment, and that segment
 /// is a Windows drive letter
 #[inline]
 pub fn is_windows_drive_letter(segment: &str) -> bool {
     segment.len() == 2 && starts_with_windows_drive_letter(segment)
 }
 
-/// Wether path starts with a root slash
+/// Whether path starts with a root slash
 /// and a windows drive letter eg: "/c:" or "/a:/"
 fn path_starts_with_windows_drive_letter(s: &str) -> bool {
-    if let Some(c) = s.as_bytes().get(0) {
+    if let Some(c) = s.as_bytes().first() {
         matches!(c, b'/' | b'\\' | b'?' | b'#') && starts_with_windows_drive_letter(&s[1..])
     } else {
         false
diff --git a/src/quirks.rs b/src/quirks.rs
index 0dbc6eb..0674ebb 100644
--- a/src/quirks.rs
+++ b/src/quirks.rs
@@ -14,6 +14,49 @@
 use crate::parser::{default_port, Context, Input, Parser, SchemeType};
 use crate::{Host, ParseError, Position, Url};
 
+/// Internal components / offsets of a URL.
+///
+/// https://user@pass:example.com:1234/foo/bar?baz#quux
+///      |      |    |          | ^^^^|       |   |
+///      |      |    |          | |   |       |   `----- fragment_start
+///      |      |    |          | |   |       `--------- query_start
+///      |      |    |          | |   `----------------- path_start
+///      |      |    |          | `--------------------- port
+///      |      |    |          `----------------------- host_end
+///      |      |    `---------------------------------- host_start
+///      |      `--------------------------------------- username_end
+///      `---------------------------------------------- scheme_end
+#[derive(Copy, Clone)]
+#[cfg(feature = "expose_internals")]
+pub struct InternalComponents {
+    pub scheme_end: u32,
+    pub username_end: u32,
+    pub host_start: u32,
+    pub host_end: u32,
+    pub port: Option<u16>,
+    pub path_start: u32,
+    pub query_start: Option<u32>,
+    pub fragment_start: Option<u32>,
+}
+
+/// Internal component / parsed offsets of the URL.
+///
+/// This can be useful for implementing efficient serialization
+/// for the URL.
+#[cfg(feature = "expose_internals")]
+pub fn internal_components(url: &Url) -> InternalComponents {
+    InternalComponents {
+        scheme_end: url.scheme_end,
+        username_end: url.username_end,
+        host_start: url.host_start,
+        host_end: url.host_end,
+        port: url.port,
+        path_start: url.path_start,
+        query_start: url.query_start,
+        fragment_start: url.fragment_start,
+    }
+}
+
 /// https://url.spec.whatwg.org/#dom-url-domaintoascii
 pub fn domain_to_ascii(domain: &str) -> String {
     match Host::parse(domain) {
@@ -138,14 +181,10 @@
         }
     }
     // Make sure we won't set an empty host to a url with a username or a port
-    if host == Host::Domain("".to_string()) {
-        if !username(&url).is_empty() {
-            return Err(());
-        } else if let Some(Some(_)) = opt_port {
-            return Err(());
-        } else if url.port().is_some() {
-            return Err(());
-        }
+    if host == Host::Domain("".to_string())
+        && (!username(url).is_empty() || matches!(opt_port, Some(Some(_))) || url.port().is_some())
+    {
+        return Err(());
     }
     url.set_host_internal(host, opt_port);
     Ok(())
@@ -177,10 +216,10 @@
                 // Empty host on special not file url
                 if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile
                     // Port with an empty host
-                    ||!port(&url).is_empty()
+                    ||!port(url).is_empty()
                     // Empty host that includes credentials
                     || !url.username().is_empty()
-                    || !url.password().unwrap_or(&"").is_empty()
+                    || !url.password().unwrap_or("").is_empty()
                 {
                     return Err(());
                 }
diff --git a/tests/data.rs b/tests/data.rs
index b72c333..f4001ca 100644
--- a/tests/data.rs
+++ b/tests/data.rs
@@ -8,7 +8,6 @@
 
 //! Data-driven tests
 
-use std::ops::Deref;
 use std::str::FromStr;
 
 use serde_json::Value;
@@ -16,7 +15,20 @@
 
 #[test]
 fn urltestdata() {
-    // Copied form https://github.com/w3c/web-platform-tests/blob/master/url/
+    let idna_skip_inputs = [
+        "http://www.foo。bar.com",
+        "http://Go.com",
+        "http://你好你好",
+        "https://faß.ExAmPlE/",
+        "http://0Xc0.0250.01",
+        "ftp://%e2%98%83",
+        "https://%e2%98%83",
+        "file://a\u{ad}b/p",
+        "file://a%C2%ADb/p",
+        "http://GOO\u{200b}\u{2060}\u{feff}goo.com",
+    ];
+
+    // Copied from https://github.com/web-platform-tests/wpt/blob/master/url/
     let mut json = Value::from_str(include_str!("urltestdata.json"))
         .expect("JSON parse error in urltestdata.json");
 
@@ -26,25 +38,39 @@
             continue; // ignore comments
         }
 
-        let base = entry.take_string("base");
+        let maybe_base = entry
+            .take_key("base")
+            .expect("missing base key")
+            .maybe_string();
         let input = entry.take_string("input");
         let failure = entry.take_key("failure").is_some();
 
-        let base = match Url::parse(&base) {
-            Ok(base) => base,
-            Err(_) if failure => continue,
-            Err(message) => {
-                eprint_failure(
-                    format!("  failed: error parsing base {:?}: {}", base, message),
-                    &format!("parse base for {:?}", input),
-                    None,
-                );
-                passed = false;
+        {
+            if idna_skip_inputs.contains(&input.as_str()) {
                 continue;
             }
+        }
+
+        let res = if let Some(base) = maybe_base {
+            let base = match Url::parse(&base) {
+                Ok(base) => base,
+                Err(_) if failure => continue,
+                Err(message) => {
+                    eprint_failure(
+                        format!("  failed: error parsing base {:?}: {}", base, message),
+                        &format!("parse base for {:?}", input),
+                        None,
+                    );
+                    passed = false;
+                    continue;
+                }
+            };
+            base.join(&input)
+        } else {
+            Url::parse(&input)
         };
 
-        let url = match (base.join(&input), failure) {
+        let url = match (res, failure) {
             (Ok(url), false) => url,
             (Err(_), true) => continue,
             (Err(message), false) => {
@@ -91,7 +117,6 @@
     assert!(passed)
 }
 
-#[allow(clippy::option_as_ref_deref)] // introduced in 1.40, MSRV is 1.36
 #[test]
 fn setters_tests() {
     let mut json = Value::from_str(include_str!("setters_tests.json"))
@@ -106,15 +131,22 @@
         let mut tests = json.take_key(attr).unwrap();
         for mut test in tests.as_array_mut().unwrap().drain(..) {
             let comment = test.take_key("comment").map(|s| s.string());
+            {
+                if let Some(comment) = comment.as_ref() {
+                    if comment.starts_with("IDNA Nontransitional_Processing") {
+                        continue;
+                    }
+                }
+            }
             let href = test.take_string("href");
             let new_value = test.take_string("new_value");
             let name = format!("{:?}.{} = {:?}", href, attr, new_value);
             let mut expected = test.take_key("expected").unwrap();
 
             let mut url = Url::parse(&href).unwrap();
-            let comment_ref = comment.as_ref().map(|s| s.deref());
+            let comment_ref = comment.as_deref();
             passed &= check_invariants(&url, &name, comment_ref);
-            let _ = set(&mut url, attr, &new_value);
+            set(&mut url, attr, &new_value);
 
             for attr in ATTRIBS {
                 if let Some(value) = expected.take_key(attr) {
@@ -153,6 +185,7 @@
 trait JsonExt {
     fn take_key(&mut self, key: &str) -> Option<Value>;
     fn string(self) -> String;
+    fn maybe_string(self) -> Option<String>;
     fn take_string(&mut self, key: &str) -> String;
 }
 
@@ -162,10 +195,14 @@
     }
 
     fn string(self) -> String {
-        if let Value::String(s) = self {
-            s
-        } else {
-            panic!("Not a Value::String")
+        self.maybe_string().expect("")
+    }
+
+    fn maybe_string(self) -> Option<String> {
+        match self {
+            Value::String(s) => Some(s),
+            Value::Null => None,
+            _ => panic!("Not a Value::String or Value::Null"),
         }
     }
 
diff --git a/tests/debugger_visualizer.rs b/tests/debugger_visualizer.rs
new file mode 100644
index 0000000..4558e07
--- /dev/null
+++ b/tests/debugger_visualizer.rs
@@ -0,0 +1,102 @@
+use debugger_test::debugger_test;
+use url::Url;
+
+#[inline(never)]
+fn __break() {}
+
+#[debugger_test(
+    debugger = "cdb",
+    commands = "
+    .nvlist
+
+    dx base_url
+
+    dx url_with_non_special_scheme
+
+    dx url_with_user_pass_port_query_fragments
+
+    dx url_blob
+
+    dx url_with_base
+
+    dx url_with_base_replaced
+
+    dx url_with_comma",
+    expected_statements = r#"
+    pattern:debugger_visualizer-.*\.exe \(embedded NatVis ".*-[0-9]+\.natvis"\)
+
+    base_url         : "http://example.org/foo/bar" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "http"
+    [host]           : "example.org"
+    [path]           : "/foo/bar"
+
+    url_with_non_special_scheme : "non-special://test/x" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "non-special"
+    [host]           : "test"
+    [path]           : "/x"
+
+    url_with_user_pass_port_query_fragments : "http://user:pass@foo:21/bar;par?b#c" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "http"
+    [username]       : "user"
+    [host]           : "foo"
+    [port]           : 21
+    [path]           : "/bar;par"
+    [query]          : "b"
+    [fragment]       : "c"
+
+    url_blob         : "blob:https://example.com:443/" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "blob"
+    [path]           : "https://example.com:443/"
+
+    url_with_base    : "http://example.org/a%2fc" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "http"
+    [host]           : "example.org"
+    [path]           : "/a%2fc"
+
+    url_with_base_replaced : "http://[::7f00:1]/" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "http"
+    [host]           : "[::7f00:1]"
+    [path]           : "/"
+
+    url_with_comma   : "data:text/html,test#test" [Type: url::Url]
+    [<Raw View>]     [Type: url::Url]
+    [scheme]         : "data"
+    [path]           : "text/html,test"
+    [fragment]       : "test"
+    "#
+)]
+fn test_url_visualizer() {
+    // Copied from https://github.com/web-platform-tests/wpt/blob/master/url/
+    let base_url = Url::parse("http://example.org/foo/bar").unwrap();
+    assert_eq!(base_url.as_str(), "http://example.org/foo/bar");
+
+    let url_with_non_special_scheme = Url::parse("non-special://:@test/x").unwrap();
+    assert_eq!(url_with_non_special_scheme.as_str(), "non-special://test/x");
+
+    let url_with_user_pass_port_query_fragments =
+        Url::parse("http://user:pass@foo:21/bar;par?b#c").unwrap();
+    assert_eq!(
+        url_with_user_pass_port_query_fragments.as_str(),
+        "http://user:pass@foo:21/bar;par?b#c"
+    );
+
+    let url_blob = Url::parse("blob:https://example.com:443/").unwrap();
+    assert_eq!(url_blob.as_str(), "blob:https://example.com:443/");
+
+    let url_with_base = base_url.join("/a%2fc").unwrap();
+    assert_eq!(url_with_base.as_str(), "http://example.org/a%2fc");
+
+    let url_with_base_replaced = base_url.join("http://[::127.0.0.1]").unwrap();
+    assert_eq!(url_with_base_replaced.as_str(), "http://[::7f00:1]/");
+
+    let url_with_comma = base_url.join("data:text/html,test#test").unwrap();
+    assert_eq!(url_with_comma.as_str(), "data:text/html,test#test");
+
+    __break();
+}
diff --git a/tests/unit.rs b/tests/unit.rs
index 13055a4..55ff59a 100644
--- a/tests/unit.rs
+++ b/tests/unit.rs
@@ -43,6 +43,14 @@
     assert_eq!(base.as_str(), "moz:/baz");
     base.set_host(Some("servo")).unwrap();
     assert_eq!(base.as_str(), "moz://servo/baz");
+
+    let mut base: Url = "file://server/share/foo/bar".parse().unwrap();
+    base.set_host(None).unwrap();
+    assert_eq!(base.as_str(), "file:///share/foo/bar");
+
+    let mut base: Url = "file://server/share/foo/bar".parse().unwrap();
+    base.set_host(Some("foo")).unwrap();
+    assert_eq!(base.as_str(), "file://foo/share/foo/bar");
 }
 
 #[test]
@@ -256,7 +264,6 @@
             0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344,
         )),
     );
-    assert_host("http://1.35.+33.49", Host::Domain("1.35.+33.49"));
     assert_host(
         "http://[::]",
         Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
@@ -271,7 +278,8 @@
     );
     assert_host("http://0x1232131", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
     assert_host("http://111", Host::Ipv4(Ipv4Addr::new(0, 0, 0, 111)));
-    assert_host("http://2..2.3", Host::Domain("2..2.3"));
+    assert!(Url::parse("http://1.35.+33.49").is_err());
+    assert!(Url::parse("http://2..2.3").is_err());
     assert!(Url::parse("http://42.0x1232131").is_err());
     assert!(Url::parse("http://192.168.0.257").is_err());
 
@@ -720,6 +728,34 @@
     }
 }
 
+#[cfg(feature = "expose_internals")]
+#[test]
+fn test_expose_internals() {
+    use url::quirks::internal_components;
+    use url::quirks::InternalComponents;
+
+    let url = Url::parse("https://example.com/path/file.ext?key=val&key2=val2#fragment").unwrap();
+    let InternalComponents {
+        scheme_end,
+        username_end,
+        host_start,
+        host_end,
+        port,
+        path_start,
+        query_start,
+        fragment_start,
+    } = internal_components(&url);
+
+    assert_eq!(scheme_end, 5);
+    assert_eq!(username_end, 8);
+    assert_eq!(host_start, 8);
+    assert_eq!(host_end, 19);
+    assert_eq!(port, None);
+    assert_eq!(path_start, 19);
+    assert_eq!(query_start, Some(33));
+    assert_eq!(fragment_start, Some(51));
+}
+
 #[test]
 fn test_windows_unc_path() {
     if !cfg!(windows) {
@@ -796,7 +832,7 @@
         ("file:/foo.txt", ExpectedFileDoubleSlash, "expected // after file:"),
         ("file://mozilla.org/c:/file.txt", FileWithHostAndWindowsDrive, "file: with host and Windows drive letter"),
         ("http://mozilla.org/^", NonUrlCodePoint, "non-URL code point"),
-        ("http://mozilla.org/#\00", NullInFragment, "NULL characters are ignored in URL fragment identifiers"),
+        ("http://mozilla.org/#\x000", NullInFragment, "NULL characters are ignored in URL fragment identifiers"),
         ("http://mozilla.org/%1", PercentDecode, "expected 2 hex digits after %"),
         ("http://mozilla.org\t/foo", TabOrNewlineIgnored, "tabs or newlines are ignored in URLs"),
         ("http://user@:[email protected]", UnencodedAtSign, "unencoded @ sign in username or password")
@@ -1081,6 +1117,16 @@
             "http://127.0.0.1:8080/test/video?baz=meh#456",
             "video?baz=meh#456",
         ),
+        (
+            "http://127.0.0.1:8080/file.txt",
+            "http://127.0.0.1:8080/test/file.txt",
+            "test/file.txt",
+        ),
+        (
+            "http://127.0.0.1:8080/not_equal.txt",
+            "http://127.0.0.1:8080/test/file.txt",
+            "test/file.txt",
+        ),
     ];
 
     for (base, uri, relative) in &tests {
@@ -1093,7 +1139,7 @@
             base, uri, relative
         );
         assert_eq!(
-            base_uri.join(&relative).unwrap().as_str(),
+            base_uri.join(relative).unwrap().as_str(),
             *uri,
             "base: {}, uri: {}, relative: {}",
             base,
diff --git a/tests/urltestdata.json b/tests/urltestdata.json
index 554e619..4265f59 100644
--- a/tests/urltestdata.json
+++ b/tests/urltestdata.json
@@ -1,6 +1,6 @@
 [
   "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js",
-  "# AS OF https://github.com/jsdom/whatwg-url/commit/35f04dfd3048cf6362f4398745bb13375c5020c2",
+  "# AS OF https://github.com/web-platform-tests/wpt/commit/2a64dae4641fbd61bd4257df460e188f425b492e",
   {
     "input": "http://example\t.\norg",
     "base": "http://example.org/foo/bar",
@@ -541,6 +541,36 @@
     "hash": ""
   },
   {
+    "input": "\\x",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/x",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\\\x\\hello",
+    "base": "http://example.org/foo/bar",
+    "href": "http://x/hello",
+    "origin": "http://x",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "x",
+    "hostname": "x",
+    "port": "",
+    "pathname": "/hello",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "::",
     "base": "http://example.org/foo/bar",
     "href": "http://example.org/foo/::",
@@ -3157,7 +3187,8 @@
   {
     "input": "http:/:@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http://user@/www.example.com",
@@ -3167,12 +3198,14 @@
   {
     "input": "http:@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http:/@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http://@/www.example.com",
@@ -3182,17 +3215,20 @@
   {
     "input": "https:@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http:a:b@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http:/a:b@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http://a:b@/www.example.com",
@@ -3202,7 +3238,8 @@
   {
     "input": "http::@/www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http:a:@www.example.com",
@@ -3267,12 +3304,14 @@
   {
     "input": "http:@:www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http:/@:www.example.com",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "http://@:www.example.com",
@@ -3646,6 +3685,38 @@
     "search": "?%EF%BF%BD",
     "hash": "#%EF%BF%BD"
   },
+  "Domain is ASCII, but a label is invalid IDNA",
+  {
+    "input": "http://a.b.c.xn--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.xn--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
+  "IDNA labels should be matched case-insensitively",
+  {
+    "input": "http://a.b.c.XN--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a.b.c.Xn--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.XN--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.xN--pokxncvks",
+    "base": "about:blank",
+    "failure": true
+  },
   "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.",
   {
     "input": "http://Go.com",
@@ -3847,21 +3918,6 @@
     "search": "",
     "hash": ""
   },
-  {
-    "input": "http://0..0x300/",
-    "base": "about:blank",
-    "href": "http://0..0x300/",
-    "origin": "http://0..0x300",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "0..0x300",
-    "hostname": "0..0x300",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
   "Broken IPv6",
   {
     "input": "http://[www.google.com]/",
@@ -4483,16 +4539,6 @@
     "hash": ""
   },
   {
-    "input": "sc://\u0000/",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
-    "input": "sc:// /",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
     "input": "sc://%/",
     "base": "about:blank",
     "href": "sc://%/",
@@ -4527,21 +4573,6 @@
     "failure": true
   },
   {
-    "input": "sc://[/",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
-    "input": "sc://\\/",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
-    "input": "sc://]/",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
     "input": "x",
     "base": "sc://ñ",
     "href": "sc://%C3%B1/x",
@@ -4619,7 +4650,7 @@
     "search": "",
     "hash": ""
   },
-  "# unknown scheme with non-URL characters in the path",
+  "# unknown scheme with non-URL characters",
   {
     "input": "wow:\uFFFF",
     "base": "about:blank",
@@ -4637,6 +4668,250 @@
   },
   "Forbidden host code points",
   {
+    "input": "sc://a\u0000b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a<b",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a>b",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a[b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a\\b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a]b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a^b",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://a|b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  "Forbidden host codepoints: tabs and newlines are removed during preprocessing",
+  {
+    "input": "foo://ho\u0009st/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://ho\u000Ast/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://ho\u000Dst/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  "Forbidden domain code-points",
+  {
+    "input": "http://a\u0000b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0001b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0002b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0003b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0004b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0005b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0006b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0007b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0008b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Bb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Cb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Eb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Fb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0010b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0011b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0012b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0013b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0014b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0015b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0016b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0017b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0018b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u0019b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Ab/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Bb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Cb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Db/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Eb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Fb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a%b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
     "input": "http://a<b",
     "base": "about:blank",
     "failure": true
@@ -4647,51 +4922,330 @@
     "failure": true
   },
   {
+    "input": "http://a[b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://a]b/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
     "input": "http://a^b",
     "base": "about:blank",
     "failure": true
   },
   {
-    "input": "non-special://a<b",
+    "input": "http://a|b/",
     "base": "about:blank",
     "failure": true
   },
   {
-    "input": "non-special://a>b",
+    "input": "http://a\u007Fb/",
+    "base": "about:blank",
+    "failure": true
+  },
+  "Forbidden domain codepoints: tabs and newlines are removed during preprocessing",
+  {
+    "input": "http://ho\u0009st/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://ho\u000Ast/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://ho\u000Dst/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  "Encoded forbidden domain codepoints in special URLs",
+  {
+    "input": "http://ho%00st/",
     "base": "about:blank",
     "failure": true
   },
   {
-    "input": "non-special://a^b",
+    "input": "http://ho%01st/",
     "base": "about:blank",
     "failure": true
   },
-  "Allowed host code points",
   {
-    "input": "http://\u001F!\"$&'()*+,-.;=_`{|}~/",
+    "input": "http://ho%02st/",
     "base": "about:blank",
-    "href": "http://\u001F!\"$&'()*+,-.;=_`{|}~/",
-    "origin": "http://\u001F!\"$&'()*+,-.;=_`{|}~",
+    "failure": true
+  },
+  {
+    "input": "http://ho%03st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%04st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%05st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%06st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%07st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%08st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%09st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Ast/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Bst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Cst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Dst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Est/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Fst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%10st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%11st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%12st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%13st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%14st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%15st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%16st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%17st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%18st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%19st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Ast/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Bst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Cst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Dst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Est/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Fst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%20st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%23st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%25st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%2Fst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Ast/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Cst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Est/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Fst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%40st/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Bst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Cst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Dst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%7Cst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://ho%7Fst/",
+    "base": "about:blank",
+    "failure": true
+  },
+  "Allowed host/domain code points",
+  {
+    "input": "http://!\"$&'()*+,-.;=_`{}~/",
+    "base": "about:blank",
+    "href": "http://!\"$&'()*+,-.;=_`{}~/",
+    "origin": "http://!\"$&'()*+,-.;=_`{}~",
     "protocol": "http:",
     "username": "",
     "password": "",
-    "host": "\u001F!\"$&'()*+,-.;=_`{|}~",
-    "hostname": "\u001F!\"$&'()*+,-.;=_`{|}~",
+    "host": "!\"$&'()*+,-.;=_`{}~",
+    "hostname": "!\"$&'()*+,-.;=_`{}~",
     "port": "",
     "pathname": "/",
     "search": "",
     "hash": ""
   },
   {
-    "input": "sc://\u001F!\"$&'()*+,-.;=_`{|}~/",
+    "input": "sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u007F!\"$%&'()*+,-.;=_`{}~/",
     "base": "about:blank",
-    "href": "sc://%1F!\"$&'()*+,-.;=_`{|}~/",
+    "href": "sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~/",
     "origin": "null",
     "protocol": "sc:",
     "username": "",
     "password": "",
-    "host": "%1F!\"$&'()*+,-.;=_`{|}~",
-    "hostname": "%1F!\"$&'()*+,-.;=_`{|}~",
+    "host": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
+    "hostname": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
     "port": "",
     "pathname": "/",
     "search": "",
@@ -4964,6 +5518,36 @@
   },
   "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
   {
+    "input": "http://1.2.3.4/",
+    "base": "http://other.com/",
+    "href": "http://1.2.3.4/",
+    "origin": "http://1.2.3.4",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "1.2.3.4",
+    "hostname": "1.2.3.4",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://1.2.3.4./",
+    "base": "http://other.com/",
+    "href": "http://1.2.3.4/",
+    "origin": "http://1.2.3.4",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "1.2.3.4",
+    "hostname": "1.2.3.4",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "http://192.168.257",
     "base": "http://other.com/",
     "href": "http://192.168.1.1/",
@@ -4979,6 +5563,21 @@
     "hash": ""
   },
   {
+    "input": "http://192.168.257.",
+    "base": "http://other.com/",
+    "href": "http://192.168.1.1/",
+    "origin": "http://192.168.1.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.1.1",
+    "hostname": "192.168.1.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "http://192.168.257.com",
     "base": "http://other.com/",
     "href": "http://192.168.257.com/",
@@ -5039,6 +5638,21 @@
     "hash": ""
   },
   {
+    "input": "http://999999999.",
+    "base": "http://other.com/",
+    "href": "http://59.154.201.255/",
+    "origin": "http://59.154.201.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "59.154.201.255",
+    "hostname": "59.154.201.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "http://999999999.com",
     "base": "http://other.com/",
     "href": "http://999999999.com/",
@@ -5119,21 +5733,6 @@
     "failure": true
   },
   {
-    "input": "http://256.256.256.256.256",
-    "base": "http://other.com/",
-    "href": "http://256.256.256.256.256/",
-    "origin": "http://256.256.256.256.256",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "256.256.256.256.256",
-    "hostname": "256.256.256.256.256",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
     "input": "https://0x.0x.0",
     "base": "about:blank",
     "href": "https://0.0.0.0/",
@@ -5188,6 +5787,56 @@
     "search": "",
     "hash": ""
   },
+  {
+    "input": "file://%43%3A",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://%43%7C",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://%43|",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://C%7C",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://%43%7C/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://%43%7C/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "asdf://%43|/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "asdf://%43%7C/",
+    "base": "about:blank",
+    "href": "asdf://%43%7C/",
+    "origin": "null",
+    "protocol": "asdf:",
+    "username": "",
+    "password": "",
+    "host": "%43%7C",
+    "hostname": "%43%7C",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
   "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)",
   {
     "input": "pix/submit.gif",
@@ -5247,6 +5896,20 @@
     "hash": ""
   },
   {
+    "input": "/",
+    "base": "file://h/a/b",
+    "href": "file://h/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "h",
+    "hostname": "h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "//d:",
     "base": "file:///C:/a/b",
     "href": "file:///d:",
@@ -5388,90 +6051,6 @@
   },
   "# File URLs and many (back)slashes",
   {
-    "input": "file:\\\\//",
-    "base": "about:blank",
-    "href": "file:///",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "file:\\\\\\\\",
-    "base": "about:blank",
-    "href": "file:///",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "file:\\\\\\\\?fox",
-    "base": "about:blank",
-    "href": "file:///?fox",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "?fox",
-    "hash": ""
-  },
-  {
-    "input": "file:\\\\\\\\#guppy",
-    "base": "about:blank",
-    "href": "file:///#guppy",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": "#guppy"
-  },
-  {
-    "input": "file://spider///",
-    "base": "about:blank",
-    "href": "file://spider/",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "spider",
-    "hostname": "spider",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "file:\\\\localhost//",
-    "base": "about:blank",
-    "href": "file:///",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
     "input": "file:///localhost//cat",
     "base": "about:blank",
     "href": "file:///localhost//cat",
@@ -5486,48 +6065,6 @@
     "hash": ""
   },
   {
-    "input": "file://\\/localhost//cat",
-    "base": "about:blank",
-    "href": "file:///localhost//cat",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/localhost//cat",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "file://localhost//a//../..//",
-    "base": "about:blank",
-    "href": "file:///",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "/////mouse",
-    "base": "file:///elephant",
-    "href": "file:///mouse",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/mouse",
-    "search": "",
-    "hash": ""
-  },
-  {
     "input": "\\//pig",
     "base": "file://lion/",
     "href": "file:///pig",
@@ -5542,48 +6079,6 @@
     "hash": ""
   },
   {
-    "input": "\\/localhost//pig",
-    "base": "file://lion/",
-    "href": "file:///pig",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/pig",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "//localhost//pig",
-    "base": "file://lion/",
-    "href": "file:///pig",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/pig",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "/..//localhost//pig",
-    "base": "file://lion/",
-    "href": "file://lion/localhost//pig",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "lion",
-    "hostname": "lion",
-    "port": "",
-    "pathname": "/localhost//pig",
-    "search": "",
-    "hash": ""
-  },
-  {
     "input": "file://",
     "base": "file://ape/",
     "href": "file:///",
@@ -5628,90 +6123,6 @@
   },
   "# Windows drive letter handling with the 'file:' base URL",
   {
-    "input": "C|",
-    "base": "file://host/dir/file",
-    "href": "file:///C:",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "C|#",
-    "base": "file://host/dir/file",
-    "href": "file:///C:#",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "C|?",
-    "base": "file://host/dir/file",
-    "href": "file:///C:?",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "C|/",
-    "base": "file://host/dir/file",
-    "href": "file:///C:/",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "C|\n/",
-    "base": "file://host/dir/file",
-    "href": "file:///C:/",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:/",
-    "search": "",
-    "hash": ""
-  },
-  {
-    "input": "C|\\",
-    "base": "file://host/dir/file",
-    "href": "file:///C:/",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/C:/",
-    "search": "",
-    "hash": ""
-  },
-  {
     "input": "C",
     "base": "file://host/dir/file",
     "href": "file://host/dir/C",
@@ -5782,24 +6193,10 @@
     "search": "",
     "hash": ""
   },
+  "# Copy the empty host from the input in the following cases",
   {
-    "input": "/c:/foo/bar",
-    "base": "file://host/path",
-    "href": "file:///c:/foo/bar",
-    "protocol": "file:",
-    "username": "",
-    "password": "",
-    "host": "",
-    "hostname": "",
-    "port": "",
-    "pathname": "/c:/foo/bar",
-    "search": "",
-    "hash": ""
-  },
-  "# Windows drive letter quirk with not empty host",
-  {
-    "input": "file://example.net/C:/",
-    "base": "about:blank",
+    "input": "//C:/",
+    "base": "file://host/",
     "href": "file:///C:/",
     "protocol": "file:",
     "username": "",
@@ -5812,8 +6209,8 @@
     "hash": ""
   },
   {
-    "input": "file://1.2.3.4/C:/",
-    "base": "about:blank",
+    "input": "file://C:/",
+    "base": "file://host/",
     "href": "file:///C:/",
     "protocol": "file:",
     "username": "",
@@ -5826,8 +6223,22 @@
     "hash": ""
   },
   {
-    "input": "file://[1::8]/C:/",
-    "base": "about:blank",
+    "input": "///C:/",
+    "base": "file://host/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///C:/",
+    "base": "file://host/",
     "href": "file:///C:/",
     "protocol": "file:",
     "username": "",
@@ -5971,7 +6382,8 @@
   {
     "input": "\\\\\\.\\Y:",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   "# file: drive letter cases from https://crbug.com/1078698 but lowercased",
   {
@@ -6033,7 +6445,51 @@
   {
     "input": "\\\\\\.\\y:",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
+  },
+  "# Additional file URL tests for (https://github.com/whatwg/url/issues/405)",
+  {
+    "input": "file:///one/two",
+    "base": "file:///",
+    "href": "file:///one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/one/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//one/two",
+    "base": "file:///",
+    "href": "file://one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "one",
+    "hostname": "one",
+    "port": "",
+    "pathname": "/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///one/two",
+    "base": "file:///",
+    "href": "file:///one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/one/two",
+    "search": "",
+    "hash": ""
   },
   "# IPv6 tests",
   {
@@ -6527,6 +6983,7 @@
     "input": "blob:https://example.com:443/",
     "base": "about:blank",
     "href": "blob:https://example.com:443/",
+    "origin": "https://example.com",
     "protocol": "blob:",
     "username": "",
     "password": "",
@@ -6541,6 +6998,7 @@
     "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
     "base": "about:blank",
     "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "origin": "null",
     "protocol": "blob:",
     "username": "",
     "password": "",
@@ -6551,21 +7009,22 @@
     "search": "",
     "hash": ""
   },
-  "Invalid IPv4 radix digits",
   {
-    "input": "http://0177.0.0.0189",
+    "input": "blob:",
     "base": "about:blank",
-    "href": "http://0177.0.0.0189/",
-    "protocol": "http:",
+    "href": "blob:",
+    "origin": "null",
+    "protocol": "blob:",
     "username": "",
     "password": "",
-    "host": "0177.0.0.0189",
-    "hostname": "0177.0.0.0189",
+    "host": "",
+    "hostname": "",
     "port": "",
-    "pathname": "/",
+    "pathname": "",
     "search": "",
     "hash": ""
   },
+  "Invalid IPv4 radix digits",
   {
     "input": "http://0x7f.0.0.0x7g",
     "base": "about:blank",
@@ -6760,17 +7219,20 @@
   {
     "input": "a",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "a/",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   {
     "input": "a//",
     "base": "about:blank",
-    "failure": true
+    "failure": true,
+    "inputCanBeRelative": true
   },
   "Bases that don't fail to parse but fail to be bases",
   {
@@ -6987,5 +7449,460 @@
     "pathname": "/",
     "search": "",
     "hash": "#link"
+  },
+  "UTF-8 percent-encode of C0 control percent-encode set and supersets",
+  {
+    "input": "non-special:cannot-be-a-base-url-\u0000\u0001\u001F\u001E\u007E\u007F\u0080",
+    "base": "about:blank",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+    "origin": "null",
+    "password": "",
+    "pathname": "cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://www.example.com/path{\u007Fpath.html?query'\u007F=query#fragment<\u007Ffragment",
+    "base": "about:blank",
+    "hash": "#fragment%3C%7Ffragment",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "href": "https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment",
+    "origin": "https://www.example.com",
+    "password": "",
+    "pathname": "/path%7B%7Fpath.html",
+    "port": "",
+    "protocol": "https:",
+    "search": "?query%27%7F=query",
+    "username": ""
+  },
+  {
+    "input": "https://user:pass[\u007F@foo/bar",
+    "base": "http://example.org",
+    "hash": "",
+    "host": "foo",
+    "hostname": "foo",
+    "href": "https://user:pass%5B%7F@foo/bar",
+    "origin": "https://foo",
+    "password": "pass%5B%7F",
+    "pathname": "/bar",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": "user"
+  },
+  "Tests for the distinct percent-encode sets",
+  {
+    "input": "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "null",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~"
+  },
+  {
+    "input": "wss:// !\"$%&'()*+,-.;<=>@[]^_`{|}~@host/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~"
+  },
+  {
+    "input": "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "null",
+    "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": "joe"
+  },
+  {
+    "input": "wss://joe: !\"$%&'()*+,-.:;<=>@[]^_`{|}~@host/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "wss://host",
+    "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": "joe"
+  },
+  {
+    "input": "foo://!\"$%&'()*+,-.;=_`{}~/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "!\"$%&'()*+,-.;=_`{}~",
+    "hostname": "!\"$%&'()*+,-.;=_`{}~",
+    "href":"foo://!\"$%&'()*+,-.;=_`{}~/",
+    "origin": "null",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://!\"$&'()*+,-.;=_`{}~/",
+    "base": "about:blank",
+    "hash": "",
+    "host": "!\"$&'()*+,-.;=_`{}~",
+    "hostname": "!\"$&'()*+,-.;=_`{}~",
+    "href":"wss://!\"$&'()*+,-.;=_`{}~/",
+    "origin": "wss://!\"$&'()*+,-.;=_`{}~",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "username": ""
+  },
+  {
+    "input": "wss://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "username": ""
+  },
+  {
+    "input": "foo://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": "about:blank",
+    "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  "Ensure that input schemes are not ignored when resolving non-special URLs",
+  {
+    "input": "abc:rootless",
+    "base": "abc://host/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:rootless",
+    "base": "abc:/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:rootless",
+    "base": "abc:path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:/rooted",
+    "base": "abc://host/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:/rooted",
+    "password": "",
+    "pathname": "/rooted",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  "Empty query and fragment with blank should throw an error",
+  {
+    "input": "#",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "?",
+    "base": null,
+    "failure": true
+  },
+  "Last component looks like a number, but not valid IPv4",
+  {
+    "input": "http://1.2.3.4.5",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.4.5.",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://0..0x300/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0..0x300./",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256.256",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256.256.",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.08",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.08.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.09",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://09.2.3.4",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://09.2.3.4.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://01.2.3.4.5",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://01.2.3.4.5.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0x100.2.3.4",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0x100.2.3.4.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0x1.2.3.4.5",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0x1.2.3.4.5.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.1.2.3.4",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.1.2.3.4.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.2.3.4",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.2.3.4.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.09",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.09.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x4",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x4.",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.09..",
+    "base": "about:blank",
+    "hash": "",
+    "host": "foo.09..",
+    "hostname": "foo.09..",
+    "href":"http://foo.09../",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://0999999999999999999/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://💩.123/",
+    "base": "about:blank",
+    "failure": true
   }
 ]