Skip to main content

script_bindings/
domstring.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![allow(clippy::non_canonical_partial_ord_impl)]
6use std::borrow::{Cow, ToOwned};
7use std::cell::{Ref, RefCell, RefMut};
8use std::default::Default;
9use std::ops::{Deref, DerefMut};
10use std::ptr::{self, NonNull};
11use std::str::FromStr;
12use std::sync::LazyLock;
13use std::{fmt, slice, str};
14
15use html5ever::{LocalName, Namespace};
16use js::context::{JSContext, NoGC};
17use js::conversions::{ToJSValConvertible, jsstr_to_string};
18use js::gc::{HandleValue, MutableHandleValue};
19use js::jsapi::{Heap, JS_GetLatin1StringCharsAndLength, JSString};
20use js::jsval::StringValue;
21use js::rust::{Runtime, Trace};
22use malloc_size_of::MallocSizeOfOps;
23use num_traits::{ToPrimitive, Zero};
24use regex::Regex;
25use servo_base::text::{AssumeUnder4GB, Utf8CodeUnits, Utf16CodeUnits};
26use style::Atom;
27use style::str::HTML_SPACE_CHARACTERS;
28use zeroize::Zeroize;
29
30use crate::trace::RootedTraceableBox;
31
32const ASCII_END: u8 = 0x7E;
33const ASCII_CAPITAL_A: u8 = 0x41;
34const ASCII_CAPITAL_Z: u8 = 0x5A;
35const ASCII_LOWERCASE_A: u8 = 0x61;
36const ASCII_LOWERCASE_Z: u8 = 0x7A;
37const ASCII_TAB: u8 = 0x09;
38const ASCII_NEWLINE: u8 = 0x0A;
39const ASCII_FORMFEED: u8 = 0x0C;
40const ASCII_CR: u8 = 0x0D;
41const ASCII_SPACE: u8 = 0x20;
42
43/// Return the Latin1 string bytes of the given `JSString`. As a garbage collection
44/// can collect this string, it must be later bound to the lifetime of a provided `NoGC`
45/// parameter. This function will never GC.
46///
47/// # Safety
48///
49/// The `*mut JSString` in `rooted_traceable_box` must be non-null. The resulting slice is
50/// guaranteed to live as long as no garbage collection operation happens. Afterwards this
51/// pointer can  point to arbitrary memory. Callers should enforce this using `NoGC`. It is
52/// essential for callers that this function not trigger a garbage collection.
53unsafe fn get_latin1_string_bytes(string: &Heap<*mut JSString>) -> &[u8] {
54    debug_assert!(!string.get().is_null());
55    let mut length = 0;
56    unsafe {
57        let chars = JS_GetLatin1StringCharsAndLength(
58            Runtime::get().expect("JS runtime has shut down").as_ptr(),
59            ptr::null(),
60            string.get(),
61            &mut length,
62        );
63        assert!(!chars.is_null());
64        slice::from_raw_parts(chars, length)
65    }
66}
67
68/// A type representing the underlying encoded bytes of a [`DOMString`].
69pub enum EncodedBytes<'a> {
70    /// These bytes are Latin1 encoded.
71    Latin1((&'a NoGC, Ref<'a, [u8]>)),
72    /// These bytes are UTF-8 encoded.
73    Utf8((&'a NoGC, Ref<'a, [u8]>)),
74}
75
76impl EncodedBytes<'_> {
77    /// Return a reference to the raw bytes of this [`EncodedBytes`] without any information about
78    /// the underlying encoding.
79    pub fn bytes(&self) -> &[u8] {
80        match self {
81            Self::Latin1(bytes) => &bytes.1,
82            Self::Utf8(bytes) => &bytes.1,
83        }
84    }
85
86    /// Return whether or not there is any data in this collection of bytes.
87    pub fn is_empty(&self) -> bool {
88        self.bytes().is_empty()
89    }
90}
91
92#[derive(Zeroize)]
93#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
94enum DOMStringType {
95    /// A simple rust string
96    Rust(String),
97    /// A JS String stored in mozjs.
98    #[zeroize(skip)]
99    JSString(Box<Heap<*mut JSString>>),
100    #[cfg(test)]
101    /// This is used for testing of the bindings to give
102    /// a raw u8 Latin1 encoded string without having a js engine.
103    Latin1Vec(Vec<u8>),
104    #[zeroize(skip)] // static strings will never have secrets.
105    RustStatic(&'static str),
106}
107
108impl Clone for DOMStringType {
109    fn clone(&self) -> Self {
110        match self {
111            Self::Rust(arg0) => Self::Rust(arg0.clone()),
112            Self::JSString(rooted_box) => Self::JSString(Heap::boxed(rooted_box.get())),
113            #[cfg(test)]
114            Self::Latin1Vec(arg0) => Self::Latin1Vec(arg0.clone()),
115            Self::RustStatic(arg0) => Self::RustStatic(arg0),
116        }
117    }
118}
119
120impl Default for DOMStringType {
121    fn default() -> Self {
122        Self::Rust(Default::default())
123    }
124}
125
126impl DOMStringType {
127    /// Warning:
128    /// This function does not check and just returns the raw bytes of the string,
129    /// whether they are utf8 or latin1.
130    /// The caller needs to take care that these make sense in context.
131    ///
132    /// # Safety
133    ///
134    /// The returned slice is only guaranteed to be valid until garbage collection happens.
135    /// Afterwards it can point to arbitrary memory.
136    unsafe fn as_raw_bytes(&self) -> &[u8] {
137        match self {
138            DOMStringType::Rust(s) => s.as_bytes(),
139            DOMStringType::JSString(rooted_traceable_box) => unsafe {
140                get_latin1_string_bytes(rooted_traceable_box)
141            },
142            #[cfg(test)]
143            DOMStringType::Latin1Vec(items) => items,
144            DOMStringType::RustStatic(s) => s.as_bytes(),
145        }
146    }
147
148    fn ensure_rust_string(&mut self) -> &mut String {
149        let new_string = match self {
150            DOMStringType::Rust(string) => return string,
151            DOMStringType::JSString(rooted_traceable_box) => {
152                let cx = unsafe { JSContext::get_from_thread() };
153                let cx = cx.as_ref().expect("JS runtime has shut down");
154                unsafe { jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap()) }
155            },
156            #[cfg(test)]
157            DOMStringType::Latin1Vec(items) => {
158                let mut v = vec![0; items.len() * 2];
159                let real_size =
160                    encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
161                v.truncate(real_size);
162
163                // Safety: convert_latin1_to_utf8 converts the raw bytes to utf8 and the
164                // buffer is the size specified in the documentation, so this should be safe.
165                unsafe { String::from_utf8_unchecked(v) }
166            },
167            // Currently because we return a `&mut String` we need to own the string.
168            DOMStringType::RustStatic(s) => s.to_owned(),
169        };
170        *self = DOMStringType::Rust(new_string);
171        self.ensure_rust_string()
172    }
173}
174
175/// A reference to a Rust `str` of UTF-8 encoded bytes, used to get a Rust
176/// string from a [`DOMString`].
177#[derive(Debug)]
178pub struct StringView<'a>(Ref<'a, str>);
179
180impl StringView<'_> {
181    pub fn split_html_space_characters(&self) -> impl Iterator<Item = &str> {
182        self.split(HTML_SPACE_CHARACTERS)
183            .filter(|string| !string.is_empty())
184    }
185}
186
187impl From<StringView<'_>> for String {
188    fn from(string_view: StringView<'_>) -> Self {
189        string_view.0.to_string()
190    }
191}
192
193impl Deref for StringView<'_> {
194    type Target = str;
195    fn deref(&self) -> &str {
196        &(self.0)
197    }
198}
199
200impl AsRef<str> for StringView<'_> {
201    fn as_ref(&self) -> &str {
202        &(self.0)
203    }
204}
205
206impl PartialEq for StringView<'_> {
207    fn eq(&self, other: &Self) -> bool {
208        self.0.eq(&*(other.0))
209    }
210}
211
212impl PartialEq<&str> for StringView<'_> {
213    fn eq(&self, other: &&str) -> bool {
214        self.0.eq(*other)
215    }
216}
217
218impl Eq for StringView<'_> {}
219
220impl PartialOrd for StringView<'_> {
221    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
222        self.0.partial_cmp(&**other)
223    }
224}
225
226impl Ord for StringView<'_> {
227    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
228        self.0.cmp(other)
229    }
230}
231
232/// Safety comment:
233///
234/// This method will _not_ trace the pointer if the rust string exists.
235/// The js string could be garbage collected and, hence, violating this
236/// could lead to undefined behavior
237unsafe impl Trace for DOMStringType {
238    unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
239        unsafe {
240            match self {
241                DOMStringType::Rust(_s) => {},
242                DOMStringType::JSString(rooted_traceable_box) => rooted_traceable_box.trace(tracer),
243                #[cfg(test)]
244                DOMStringType::Latin1Vec(_s) => {},
245                DOMStringType::RustStatic(_) => {},
246            }
247        }
248    }
249}
250
251impl malloc_size_of::MallocSizeOf for DOMStringType {
252    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
253        match self {
254            DOMStringType::Rust(s) => s.size_of(ops),
255            DOMStringType::JSString(_rooted_traceable_box) => {
256                // Managed by JS Engine
257                0
258            },
259            #[cfg(test)]
260            DOMStringType::Latin1Vec(s) => s.size_of(ops),
261            DOMStringType::RustStatic(_s) => 0,
262        }
263    }
264}
265
266impl std::fmt::Debug for DOMStringType {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self {
269            DOMStringType::Rust(s) => f.debug_struct("DOMString").field("rust_string", s).finish(),
270            DOMStringType::JSString(_rooted_traceable_box) => f.debug_struct("DOMString").finish(),
271            #[cfg(test)]
272            DOMStringType::Latin1Vec(s) => f
273                .debug_struct("DOMString")
274                .field("latin1_string", s)
275                .finish(),
276            DOMStringType::RustStatic(s) => f
277                .debug_struct("DOMString")
278                .field("static_string", s)
279                .finish(),
280        }
281    }
282}
283
284////// A DOMString.
285///
286/// This type corresponds to the [`DOMString`] type in WebIDL.
287///
288/// [`DOMString`]: https://webidl.spec.whatwg.org/#idl-DOMString
289///
290/// Conceptually, a DOMString has the same value space as a JavaScript String,
291/// i.e., an array of 16-bit *code units* representing UTF-16, potentially with
292/// unpaired surrogates present (also sometimes called WTF-16).
293///
294/// However, Rust `String`s are guaranteed to be valid UTF-8, and as such have
295/// a *smaller value space* than WTF-16 (i.e., some JavaScript String values
296/// can not be represented as a Rust `String`). This introduces the question of
297/// what to do with values being passed from JavaScript to Rust that contain
298/// unpaired surrogates.
299///
300/// The hypothesis is that it does not matter much how exactly those values are
301/// transformed, because  passing unpaired surrogates into the DOM is very rare.
302/// Instead Servo will replace the unpaired surrogate by a U+FFFD replacement
303/// character.
304///
305/// Currently, the lack of crash reports about this issue provides some
306/// evidence to support the hypothesis. This evidence will hopefully be used to
307/// convince other browser vendors that it would be safe to replace unpaired
308/// surrogates at the boundary between JavaScript and native code. (This would
309/// unify the `DOMString` and `USVString` types, both in the WebIDL standard
310/// and in Servo.)
311///
312/// Note: the above appears refuted by the needs of `dom/encoding`.
313///
314/// This string class will keep either the Reference to the mozjs object alive
315/// or will have an internal rust string.
316/// We currently default to doing most of the string operation on the rust side.
317/// You should use `str()` to get the Rust string (represented by a `StringView`
318/// which you can deref to a `&str`). You should assume that this conversion is
319/// expensive. For now, you should assume that all the functions incur this
320/// conversion cost.
321#[repr(transparent)]
322#[derive(Clone, Debug, Default, MallocSizeOf, JSTraceable)]
323#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
324pub struct TracedDOMString(RefCell<DOMStringType>);
325
326// TODO: Removed RootedTraceableBox if TracedDOMString is not a js type.
327#[derive(Default, MallocSizeOf, JSTraceable)]
328pub struct DOMString(RootedTraceableBox<TracedDOMString>);
329
330impl std::fmt::Debug for DOMString {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        f.debug_tuple("DOMString").field(&*self.0).finish()
333    }
334}
335
336impl DerefMut for DOMString {
337    fn deref_mut(&mut self) -> &mut Self::Target {
338        &mut self.0
339    }
340}
341
342impl Deref for DOMString {
343    type Target = TracedDOMString;
344
345    fn deref(&self) -> &Self::Target {
346        &self.0
347    }
348}
349
350impl Clone for DOMString {
351    fn clone(&self) -> Self {
352        self.0.clone().root()
353    }
354}
355
356impl DOMString {
357    /// Creates a new `DOMString`.
358    pub fn new() -> DOMString {
359        TracedDOMString::default().root()
360    }
361
362    /// Creates the string from js. If the string can be encoded in latin1, just take the reference
363    /// to the JSString. Otherwise do the conversion to utf8 now.
364    /// <https://webidl.spec.whatwg.org/#js-DOMString>
365    pub fn from_js_string(
366        cx: &mut JSContext,
367        value: HandleValue,
368    ) -> Result<DOMString, DOMStringErrorType> {
369        // Step 1: If V is null
370        // and the conversion is to an IDL type associated
371        // with the [LegacyNullToEmptyString] extended attribute,
372        // then return the DOMString value that represents the empty string.
373        // TODO.
374
375        // Step 2: Let x be ? ToString(V).
376        // Step 3: Return the IDL DOMString value that represents the same sequence of
377        // code units as the one the JavaScript String value x represents.
378        // Note: this implementation does not preserve the same sequence of code units.
379        rooted!(&in(cx) let string_ptr = unsafe { js::rust::ToString(cx, value) });
380        unsafe { DOMString::from_raw_js_string(cx, *string_ptr) }
381    }
382
383    /// Takes a raw `*mut JSString` and creates a [`DOMString`] out of it. Prefer [`DOMString::from_js_string`] if you have a `HandleValue`.
384    /// Seee [`DOMString::from_js_string`] for more detail.
385    ///
386    /// # Safety
387    ///
388    /// *mut JSString needs to point to a valid JSString.
389    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
390    pub unsafe fn from_raw_js_string(
391        cx: &JSContext,
392        string_ptr: *mut JSString,
393    ) -> Result<DOMString, DOMStringErrorType> {
394        if string_ptr.is_null() {
395            debug!("ToString failed");
396            Err(DOMStringErrorType::JSConversionError)
397        } else {
398            let latin1 = unsafe { js::jsapi::JS_DeprecatedStringHasLatin1Chars(string_ptr) };
399            let inner = if latin1 {
400                let h = Heap::boxed(string_ptr);
401                DOMStringType::JSString(h)
402            } else {
403                // We need to convert the string anyway as it is not just latin1
404                DOMStringType::Rust(unsafe {
405                    jsstr_to_string(cx, NonNull::new(string_ptr).unwrap())
406                })
407            };
408            Ok(TracedDOMString(RefCell::new(inner)).root())
409        }
410    }
411
412    /// Creates a DOMString from a `&'static str` reference. More efficient than allocating the string.
413    pub fn from_static(s: &'static str) -> DOMString {
414        TracedDOMString(RefCell::new(DOMStringType::RustStatic(s))).root()
415    }
416
417    pub fn traced(self) -> TracedDOMString {
418        *self.0.into_box()
419    }
420
421    pub fn str(&self) -> StringView<'_> {
422        self.0.str()
423    }
424}
425
426#[derive(Debug)]
427pub enum DOMStringErrorType {
428    JSConversionError,
429}
430
431impl From<String> for TracedDOMString {
432    fn from(string: String) -> Self {
433        TracedDOMString(RefCell::new(DOMStringType::Rust(string)))
434    }
435}
436
437impl TracedDOMString {
438    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
439    pub fn root(self) -> DOMString {
440        DOMString(RootedTraceableBox::from_box(Box::new(self)))
441    }
442
443    /// Transforms the internal storage of this [`DOMString`] into a Rust string if it is not
444    /// yet one. This will make a copy of the underlying string data.
445    fn ensure_rust_string(&self) -> RefMut<'_, String> {
446        let inner = self.0.borrow_mut();
447        RefMut::map(inner, |inner| inner.ensure_rust_string())
448    }
449
450    /// Debug the current  state of the string without modifying it.
451    #[expect(unused)]
452    fn debug_js(&self, cx: &JSContext) {
453        match *self.0.borrow() {
454            DOMStringType::Rust(ref s) => info!("Rust String ({})", s),
455            DOMStringType::JSString(ref rooted_traceable_box) => {
456                let s = unsafe {
457                    jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap())
458                };
459                info!("JSString ({})", s);
460            },
461            #[cfg(test)]
462            DOMStringType::Latin1Vec(ref items) => info!("Latin1 string"),
463            DOMStringType::RustStatic(s) => info!("Static Rust String ({})", s),
464        }
465    }
466
467    /// Returns the underlying rust string.
468    pub fn str(&self) -> StringView<'_> {
469        {
470            let inner = self.0.borrow();
471            if matches!(&*inner, DOMStringType::Rust(..)) {
472                return StringView(Ref::map(inner, |inner| match inner {
473                    DOMStringType::Rust(string) => string.as_str(),
474                    _ => unreachable!("Guaranteed by condition above"),
475                }));
476            }
477        }
478
479        self.ensure_rust_string();
480        self.str()
481    }
482
483    /// Return the [`EncodedBytes`] of this [`DOMString`]. This returns the original encoded
484    /// bytes of the string without doing any conversions.
485    pub fn encoded_bytes<'a>(&'a self, no_gc: &'a NoGC) -> EncodedBytes<'a> {
486        let inner = self.0.borrow();
487        // The lifetime of the `EncodedBytes` struct is bound by the lifetime of the `NoGC` token.
488        match &*inner {
489            DOMStringType::Rust(..) | DOMStringType::RustStatic(..) => EncodedBytes::Utf8((
490                no_gc,
491                Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
492            )),
493            DOMStringType::JSString(..) => EncodedBytes::Latin1((
494                no_gc,
495                Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
496            )),
497            #[cfg(test)]
498            DOMStringType::Latin1Vec(..) => EncodedBytes::Latin1((
499                no_gc,
500                Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
501            )),
502        }
503    }
504
505    pub fn clear(&mut self) {
506        let mut inner = self.0.borrow_mut();
507        let DOMStringType::Rust(string) = &mut *inner else {
508            *inner = DOMStringType::Rust(String::new());
509            return;
510        };
511        string.clear();
512    }
513
514    pub fn is_empty(&self) -> bool {
515        // This is safe as the bytes do not leave this function and no gc can happen.
516        let no_gc = unsafe { NoGC::new() };
517        self.encoded_bytes(&no_gc).is_empty()
518    }
519
520    /// The length of this string in UTF-8 code units, each one being one byte in size.
521    /// This method is the same as [`DOMString::len`], but the result is wrapped in a
522    /// `Utf8CodeUnits` to be used in code that mixes different kinds of offsets.
523    ///
524    /// Note: This is different than the number of Unicode characters (or code points). A
525    /// character may require multiple UTF-8 code units.
526    pub fn len_utf8(&self) -> Utf8CodeUnits {
527        // TODO: add a check that DOMString values never exceed 2 GiB?
528        // This is safe as the bytes do not leave this function and no GC can happen.
529        let no_gc = unsafe { NoGC::new() };
530        Utf8CodeUnits(match self.encoded_bytes(&no_gc) {
531            EncodedBytes::Utf8(bytes) => bytes.1.len() as u32,
532            EncodedBytes::Latin1(bytes) => bytes
533                .1
534                .iter()
535                // Latin-1 bytes 0x00 to 0x7F are ASCII-compatible and UTF-8-compatible
536                // Latin-1 bytes 0x80 to 0xFF convert to two-byte UTF-8 sequences
537                .map(|&byte| if byte < 128 { 1 } else { 2 })
538                .sum::<u32>(),
539        })
540    }
541
542    /// Returns a length for “is small” heuristics, whose precise definition does not matter
543    pub fn len_utf8_or_latin1(&self) -> usize {
544        // This is safe as the bytes do not leave this function and no GC can happen.
545        let no_gc = unsafe { NoGC::new() };
546        match self.encoded_bytes(&no_gc) {
547            EncodedBytes::Utf8(bytes) => bytes.1.len(),
548            EncodedBytes::Latin1(bytes) => bytes.1.len(),
549        }
550    }
551
552    /// The length of this string in UTF-16 code units, each one being one two bytes in size.
553    ///
554    /// Note: This is different than the number of Unicode characters (or code points). A
555    /// character may require multiple UTF-16 code units.
556    pub fn len_utf16(&self) -> Utf16CodeUnits {
557        // This is safe as the bytes do not leave this function and no GC can happen.
558        let no_gc = unsafe { NoGC::new() };
559        match self.encoded_bytes(&no_gc) {
560            // All Latin-1 characters encode to a single UTF-16 code unit.
561            EncodedBytes::Latin1(bytes) => Utf16CodeUnits(bytes.1.len() as u32),
562            // TODO: add a check that DOMString values never exceed 2 GiB?
563            // SAFETY: These are the bytes of a UTF-8 string, so they can be interpreted as UTF-8.
564            EncodedBytes::Utf8(bytes) => Utf16CodeUnits::length_of(AssumeUnder4GB, unsafe {
565                str::from_utf8_unchecked(&bytes.1)
566            }),
567        }
568    }
569
570    /// This works the same as `make_ascii_lowercase` on std::string. This means that any character in [A-Z]
571    /// will be transformed to lower case but other characters stay the same (either ASCII or not ASCII).
572    pub fn make_ascii_lowercase(&mut self) {
573        self.0
574            .borrow_mut()
575            .ensure_rust_string()
576            .make_ascii_lowercase();
577    }
578
579    pub fn push_str(&mut self, string_to_push: &str) {
580        self.0
581            .borrow_mut()
582            .ensure_rust_string()
583            .push_str(string_to_push);
584    }
585
586    /// <https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace>
587    pub fn strip_leading_and_trailing_ascii_whitespace(&mut self) {
588        if self.is_empty() {
589            return;
590        }
591
592        let mut inner = self.0.borrow_mut();
593        let string = inner.ensure_rust_string();
594        let trailing_whitespace_len = string
595            .trim_end_matches(|character: char| character.is_ascii_whitespace())
596            .len();
597        string.truncate(trailing_whitespace_len);
598        if string.is_empty() {
599            return;
600        }
601
602        let first_non_whitespace = string
603            .find(|character: char| !character.is_ascii_whitespace())
604            .unwrap();
605        string.replace_range(0..first_non_whitespace, "");
606    }
607
608    /// <https://html.spec.whatwg.org/multipage/#valid-floating-point-number>
609    pub fn is_valid_floating_point_number_string(&self) -> bool {
610        is_valid_floating_point_number_string(self.0.borrow_mut().ensure_rust_string()) &&
611            self.parse_floating_point_number().is_some()
612    }
613
614    pub fn parse<T: FromStr>(&self) -> Result<T, <T as FromStr>::Err> {
615        self.str().parse::<T>()
616    }
617
618    /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-floating-point-number-values>
619    pub fn parse_floating_point_number(&self) -> Option<f64> {
620        parse_floating_point_number(&self.str())
621    }
622
623    /// <https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number>
624    pub fn set_best_representation_of_the_floating_point_number(&mut self) {
625        if let Some(val) = self.parse_floating_point_number() {
626            // [tc39] Step 2: If x is either +0 or -0, return "0".
627            let parsed_value = if val.is_zero() { 0.0_f64 } else { val };
628
629            *self.0.borrow_mut() = DOMStringType::Rust(parsed_value.to_string());
630        }
631    }
632
633    pub fn to_lowercase(&self) -> String {
634        self.str().to_lowercase()
635    }
636
637    pub fn to_uppercase(&self) -> String {
638        self.str().to_uppercase()
639    }
640
641    pub fn strip_newlines(&mut self) {
642        // > To strip newlines from a string, remove any U+000A LF and U+000D CR code
643        // > points from the string.
644        self.0
645            .borrow_mut()
646            .ensure_rust_string()
647            .retain(|character| character != '\r' && character != '\n');
648    }
649
650    /// Normalize newlines according to <https://infra.spec.whatwg.org/#normalize-newlines>.
651    pub fn normalize_newlines(&mut self) {
652        // > To normalize newlines in a string, replace every U+000D CR U+000A LF code point
653        // > pair with a single U+000A LF code point, and then replace every remaining
654        // > U+000D CR code point with a U+000A LF code point.
655        let mut inner = self.0.borrow_mut();
656        let string = inner.ensure_rust_string();
657        *string = string.replace("\r\n", "\n").replace("\r", "\n")
658    }
659
660    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
661    pub fn replace(self, needle: &str, replace_char: &str) -> DOMString {
662        let new_string = self.str().to_owned();
663        TracedDOMString(RefCell::new(DOMStringType::Rust(
664            new_string.replace(needle, replace_char),
665        )))
666        .root()
667    }
668
669    /// Pattern is not yet stable in rust, hence, we need different methods for str and char
670    pub fn starts_with(&self, c: char) -> bool {
671        if !c.is_ascii() {
672            self.str().starts_with(c)
673        } else {
674            // As this is an ASCII character, it is guaranteed to be a single byte, no matter if the
675            // underlying encoding is UTF-8 or Latin1.
676            // This is safe as the bytes do not leave this function and no GC can happen.
677            let no_gc = unsafe { NoGC::new() };
678            self.encoded_bytes(&no_gc).bytes().starts_with(&[c as u8])
679        }
680    }
681
682    pub fn starts_with_str(&self, needle: &str) -> bool {
683        self.str().starts_with(needle)
684    }
685
686    pub fn ends_with_str(&self, needle: &str) -> bool {
687        self.str().ends_with(needle)
688    }
689
690    pub fn contains(&self, needle: &str) -> bool {
691        self.str().contains(needle)
692    }
693
694    /// Returns whether this [`DOMString`] is an ASCII case-insensitive match for `other`,
695    /// without allocating and copying temporaries.
696    ///
697    /// <https://infra.spec.whatwg.org/#ascii-case-insensitive>
698    pub fn eq_ignore_ascii_case(&self, other: &str) -> bool {
699        if other.is_ascii() {
700            // This is safe as the bytes do not leave this function and no GC can happen.
701            let no_gc = unsafe { NoGC::new() };
702            self.encoded_bytes(&no_gc)
703                .bytes()
704                .eq_ignore_ascii_case(other.as_bytes())
705        } else {
706            self.str().eq_ignore_ascii_case(other)
707        }
708    }
709
710    pub fn to_ascii_lowercase(&self) -> String {
711        // This is safe as the bytes do not leave this function and no GC can happen. The bytes actually get copiied.
712        let no_gc = unsafe { NoGC::new() };
713        let conversion = match self.encoded_bytes(&no_gc) {
714            EncodedBytes::Latin1(bytes) => {
715                if bytes.1.iter().all(|c| *c <= ASCII_END) {
716                    // We are just simple ascii
717                    Some(unsafe {
718                        String::from_utf8_unchecked(
719                            bytes
720                                .1
721                                .iter()
722                                .map(|c| {
723                                    if *c >= ASCII_CAPITAL_A && *c <= ASCII_CAPITAL_Z {
724                                        c + 32
725                                    } else {
726                                        *c
727                                    }
728                                })
729                                .collect(),
730                        )
731                    })
732                } else {
733                    None
734                }
735            },
736            EncodedBytes::Utf8(bytes) => unsafe {
737                // Safe because we know it was a utf8 string
738                Some(str::from_utf8_unchecked(&bytes.1).to_ascii_lowercase())
739            },
740        };
741        // We otherwise would double borrow the refcell
742        if let Some(conversion) = conversion {
743            conversion
744        } else {
745            self.str().to_ascii_lowercase()
746        }
747    }
748
749    fn contains_space_characters(
750        &self,
751        latin1_characters: &'static [u8],
752        utf8_characters: &'static [char],
753    ) -> bool {
754        // This is safe as the bytes do not leave this function and no GC can happen.
755        let no_gc = unsafe { NoGC::new() };
756        match self.encoded_bytes(&no_gc) {
757            EncodedBytes::Latin1(items) => {
758                latin1_characters.iter().any(|byte| items.1.contains(byte))
759            },
760            EncodedBytes::Utf8(bytes) => {
761                // Save because we know it was a utf8 string
762                let s = unsafe { str::from_utf8_unchecked(&bytes.1) };
763                s.contains(utf8_characters)
764            },
765        }
766    }
767
768    /// <https://infra.spec.whatwg.org/#ascii-tab-or-newline>
769    pub fn contains_tab_or_newline(&self) -> bool {
770        const LATIN_TAB_OR_NEWLINE: [u8; 3] = [ASCII_TAB, ASCII_NEWLINE, ASCII_CR];
771        const UTF8_TAB_OR_NEWLINE: [char; 3] = ['\u{0009}', '\u{000a}', '\u{000d}'];
772
773        self.contains_space_characters(&LATIN_TAB_OR_NEWLINE, &UTF8_TAB_OR_NEWLINE)
774    }
775
776    /// <https://infra.spec.whatwg.org/#ascii-whitespace>
777    pub fn contains_html_space_characters(&self) -> bool {
778        const SPACE_BYTES: [u8; 5] = [
779            ASCII_TAB,
780            ASCII_NEWLINE,
781            ASCII_FORMFEED,
782            ASCII_CR,
783            ASCII_SPACE,
784        ];
785        self.contains_space_characters(&SPACE_BYTES, HTML_SPACE_CHARACTERS)
786    }
787
788    /// This returns the string in utf8 bytes, i.e., `[u8]` encoded with utf8.
789    pub fn as_bytes<'a>(&'a self, no_gc: &'a NoGC) -> BytesView<'a> {
790        // BytesView will just give the raw bytes on dereference.
791        // If we are ascii this is the same for latin1 and utf8.
792        // Otherwise we convert to rust.
793        if self.is_ascii() {
794            BytesView {
795                _no_gc: no_gc,
796                inner: self.0.borrow(),
797            }
798        } else {
799            self.ensure_rust_string();
800            BytesView {
801                _no_gc: no_gc,
802                inner: self.0.borrow(),
803            }
804        }
805    }
806
807    /// Tests if there are only ascii lowercase characters. Does not include special characters.
808    pub fn is_ascii_lowercase(&self) -> bool {
809        // This is safe as the bytes do not leave this function and no GC can happen.
810        let no_gc = unsafe { NoGC::new() };
811        match self.encoded_bytes(&no_gc) {
812            EncodedBytes::Latin1(items) => items
813                .1
814                .iter()
815                .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(c)),
816            EncodedBytes::Utf8(bytes) => bytes
817                .1
818                .iter()
819                .map(|c| c.to_u8().unwrap_or(ASCII_LOWERCASE_A - 1))
820                .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(&c)),
821        }
822    }
823
824    /// Is the string only ascii characters
825    pub fn is_ascii(&self) -> bool {
826        // This is safe as the bytes do not leave this function and no GC can happen.
827        let no_gc = unsafe { NoGC::new() };
828        self.encoded_bytes(&no_gc).bytes().is_ascii()
829    }
830
831    /// Returns true if the slice only contains bytes that are safe to use in cookie strings.
832    /// <https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6-6>
833    /// Not using ServoCookie::is_valid_name_or_value to prevent dependency on the net crate.
834    pub fn is_valid_for_cookie(&self) -> bool {
835        // This is safe as the bytes do not leave this function and no GC can happen.
836        let no_gc = unsafe { NoGC::new() };
837        match self.encoded_bytes(&no_gc) {
838            EncodedBytes::Latin1(items) | EncodedBytes::Utf8(items) => !items
839                .1
840                .iter()
841                .any(|c| *c == 0x7f || (*c <= 0x1f && *c != 0x09)),
842        }
843    }
844
845    /// Call the callback with a `&str` reference of the string stored in this [`DOMString`]. Note
846    /// that if the [`DOMString`] cannot be interpreted as a Rust string a conversion will be done.
847    fn with_str_reference<Result>(&self, callback: fn(&str) -> Result) -> Result {
848        // This is safe as the bytes do not leave this function and no GC can happen. The result always has a
849        // different lifetime which is enforced by creating the no_gc in this scope.
850        let no_gc = unsafe { NoGC::new() };
851        match self.encoded_bytes(&no_gc) {
852            // If the Latin1 string is all ASCII bytes, then it is safe to interpret it as UTF-8.
853            EncodedBytes::Latin1(latin1_bytes) => {
854                if latin1_bytes.1.iter().all(|character| character.is_ascii()) {
855                    // SAFETY: All characters are ASCII, so it is safe to interpret this string as
856                    // UTF-8.
857                    return callback(unsafe { str::from_utf8_unchecked(&latin1_bytes.1) });
858                }
859            },
860            EncodedBytes::Utf8(utf8_bytes) => {
861                // SAFETY: These are the bytes of a UTF-8 string already, so they can be interpreted
862                // as UTF-8.
863                return callback(unsafe { str::from_utf8_unchecked(&utf8_bytes.1) });
864            },
865        };
866        callback(self.str().deref())
867    }
868
869    /// Newline replacement routine as described in step 1 of the multipart/form-data
870    /// encoding algorithm and many steps of application/x-www-form-urlencoded.
871    /// e.g. <https://html.spec.whatwg.org/multipage/#convert-to-a-list-of-name-value-pairs>
872    ///
873    /// Replace every occurrence of U+000D (CR) not followed by U+000A (LF),
874    /// and every occurrence of U+000A (LF) not preceded by U+000D (CR), in entry's name,
875    /// by a string consisting of a U+000D (CR) and U+000A (LF).
876    pub fn normalize_crlf(&self) -> String {
877        let s = self.str();
878        let mut buf = String::new();
879        let mut prev = ' ';
880        for ch in s.chars() {
881            match ch {
882                '\n' if prev != '\r' => {
883                    buf.push('\r');
884                    buf.push('\n');
885                },
886                '\n' => {
887                    buf.push('\n');
888                },
889                // This character isn't LF but is
890                // preceded by CR
891                _ if prev == '\r' => {
892                    buf.push('\n');
893                    buf.push(ch);
894                },
895                _ => buf.push(ch),
896            };
897            prev = ch;
898        }
899        // In case the last character was CR
900        if prev == '\r' {
901            buf.push('\n');
902        }
903        buf
904    }
905}
906
907/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-floating-point-number-values>
908pub fn parse_floating_point_number(input: &str) -> Option<f64> {
909    // Steps 15-16 are telling us things about IEEE rounding modes
910    // for floating-point significands; this code assumes the Rust
911    // compiler already matches them in any cases where
912    // that actually matters. They are not
913    // related to f64::round(), which is for rounding to integers.
914    input.trim().parse::<f64>().ok().filter(|value| {
915        // A valid number is the same as what rust considers to be valid,
916        // except for +1., NaN, and Infinity.
917        !(value.is_infinite() || value.is_nan() || input.ends_with('.') || input.starts_with('+'))
918    })
919}
920
921pub struct BytesView<'a> {
922    _no_gc: &'a NoGC,
923    inner: Ref<'a, DOMStringType>,
924}
925
926impl Deref for BytesView<'_> {
927    type Target = [u8];
928
929    fn deref(&self) -> &Self::Target {
930        // The lifetime of BytesView is bounded by the lifetime of a `NoGC` token.
931        unsafe { self.inner.as_raw_bytes() }
932    }
933}
934
935impl Ord for TracedDOMString {
936    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
937        self.str().cmp(&other.str())
938    }
939}
940
941impl PartialOrd for TracedDOMString {
942    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
943        self.str().partial_cmp(&other.str())
944    }
945}
946
947impl PartialOrd for DOMString {
948    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
949        self.0.partial_cmp(&other.0)
950    }
951}
952
953impl Extend<char> for TracedDOMString {
954    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
955        self.0.borrow_mut().ensure_rust_string().extend(iter)
956    }
957}
958
959impl ToJSValConvertible for DOMString {
960    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
961        let val = self.0.0.borrow();
962        match *val {
963            DOMStringType::Rust(ref s) => s.to_jsval(cx, rval),
964            DOMStringType::JSString(ref rooted_traceable_box) => unsafe {
965                rval.set(StringValue(&*rooted_traceable_box.get()));
966            },
967            #[cfg(test)]
968            DOMStringType::Latin1Vec(ref items) => {
969                let mut v = vec![0; items.len() * 2];
970                let real_size =
971                    encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
972                v.truncate(real_size);
973
974                String::from_utf8(v)
975                    .expect("Error in constructin test string")
976                    .to_jsval(cx, rval);
977            },
978            DOMStringType::RustStatic(s) => s.to_jsval(cx, rval),
979        };
980    }
981}
982
983impl std::hash::Hash for TracedDOMString {
984    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
985        self.str().hash(state);
986    }
987}
988
989impl std::hash::Hash for DOMString {
990    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
991        self.str().hash(state);
992    }
993}
994
995impl std::fmt::Display for TracedDOMString {
996    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
997        fmt::Display::fmt(self.str().deref(), f)
998    }
999}
1000
1001impl std::fmt::Display for DOMString {
1002    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1003        fmt::Display::fmt(self.str().deref(), f)
1004    }
1005}
1006
1007impl std::cmp::PartialEq<str> for TracedDOMString {
1008    fn eq(&self, other: &str) -> bool {
1009        if other.is_ascii() {
1010            // This is safe as the bytes do not leave this function and no GC can happen.
1011            let no_gc = unsafe { NoGC::new() };
1012            *other.as_bytes() == *self.encoded_bytes(&no_gc).bytes()
1013        } else {
1014            self.str().deref() == other
1015        }
1016    }
1017}
1018
1019impl std::cmp::PartialEq<str> for DOMString {
1020    fn eq(&self, other: &str) -> bool {
1021        self.0.eq(other)
1022    }
1023}
1024
1025impl std::cmp::PartialEq<&str> for TracedDOMString {
1026    fn eq(&self, other: &&str) -> bool {
1027        self.eq(*other)
1028    }
1029}
1030
1031impl std::cmp::PartialEq<&str> for DOMString {
1032    fn eq(&self, other: &&str) -> bool {
1033        self.0.eq(*other)
1034    }
1035}
1036
1037impl std::cmp::PartialEq<String> for TracedDOMString {
1038    fn eq(&self, other: &String) -> bool {
1039        self.eq(other.as_str())
1040    }
1041}
1042
1043impl std::cmp::PartialEq<String> for DOMString {
1044    fn eq(&self, other: &String) -> bool {
1045        self.0.eq(other.as_str())
1046    }
1047}
1048
1049impl std::cmp::PartialEq<TracedDOMString> for String {
1050    fn eq(&self, other: &TracedDOMString) -> bool {
1051        other.eq(self)
1052    }
1053}
1054
1055impl std::cmp::PartialEq<DOMString> for String {
1056    fn eq(&self, other: &DOMString) -> bool {
1057        other.0.eq(self)
1058    }
1059}
1060
1061impl std::cmp::PartialEq<TracedDOMString> for str {
1062    fn eq(&self, other: &TracedDOMString) -> bool {
1063        other.eq(self)
1064    }
1065}
1066
1067impl std::cmp::PartialEq<DOMString> for str {
1068    fn eq(&self, other: &DOMString) -> bool {
1069        other.0.eq(self)
1070    }
1071}
1072
1073impl std::cmp::PartialEq for TracedDOMString {
1074    fn eq(&self, other: &TracedDOMString) -> bool {
1075        let no_gc = unsafe { NoGC::new() };
1076        let result = match (self.encoded_bytes(&no_gc), other.encoded_bytes(&no_gc)) {
1077            (EncodedBytes::Latin1(bytes), EncodedBytes::Latin1(other_bytes)) => {
1078                Some(*bytes.1 == *other_bytes.1)
1079            },
1080            (EncodedBytes::Latin1(bytes), EncodedBytes::Utf8(other_bytes))
1081                if other_bytes.1.is_ascii() =>
1082            {
1083                Some(*bytes.1 == *other_bytes.1)
1084            },
1085            (EncodedBytes::Utf8(bytes), EncodedBytes::Latin1(other_bytes))
1086                if bytes.1.is_ascii() =>
1087            {
1088                Some(*bytes.1 == *other_bytes.1)
1089            },
1090            (EncodedBytes::Utf8(bytes), EncodedBytes::Utf8(other_bytes)) => {
1091                Some(*bytes.1 == *other_bytes.1)
1092            },
1093            _ => None,
1094        };
1095
1096        if let Some(eq_result) = result {
1097            return eq_result;
1098        }
1099
1100        *self.str() == *other.str()
1101    }
1102}
1103
1104impl std::cmp::PartialEq for DOMString {
1105    fn eq(&self, other: &Self) -> bool {
1106        *self.0 == *other.0
1107    }
1108}
1109
1110impl std::cmp::Eq for TracedDOMString {}
1111
1112impl std::cmp::Eq for DOMString {}
1113
1114impl From<std::string::String> for DOMString {
1115    fn from(string: String) -> Self {
1116        TracedDOMString(RefCell::new(DOMStringType::Rust(string))).root()
1117    }
1118}
1119
1120/// If you have a static str use the provided `DOMString::from_static`.
1121impl From<&str> for DOMString {
1122    fn from(string: &str) -> Self {
1123        String::from(string).into()
1124    }
1125}
1126
1127impl From<DOMString> for LocalName {
1128    fn from(dom_string: DOMString) -> LocalName {
1129        dom_string.with_str_reference(|string| LocalName::from(string))
1130    }
1131}
1132
1133impl From<&DOMString> for LocalName {
1134    fn from(dom_string: &DOMString) -> LocalName {
1135        dom_string.with_str_reference(|string| LocalName::from(string))
1136    }
1137}
1138
1139impl From<DOMString> for Namespace {
1140    fn from(dom_string: DOMString) -> Namespace {
1141        dom_string.with_str_reference(|string| Namespace::from(string))
1142    }
1143}
1144
1145impl From<DOMString> for Atom {
1146    fn from(dom_string: DOMString) -> Atom {
1147        dom_string.with_str_reference(|string| Atom::from(string))
1148    }
1149}
1150
1151#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1152impl From<TracedDOMString> for String {
1153    fn from(val: TracedDOMString) -> Self {
1154        val.ensure_rust_string();
1155        let inner = val.0.take();
1156        match inner {
1157            DOMStringType::Rust(s) => s,
1158            DOMStringType::JSString(_) => unreachable!(),
1159            #[cfg(test)]
1160            DOMStringType::Latin1Vec(items) => String::from_utf8(items).expect("Not valid latin1"),
1161            DOMStringType::RustStatic(s) => s.to_owned(),
1162        }
1163    }
1164}
1165
1166impl From<DOMString> for String {
1167    fn from(value: DOMString) -> Self {
1168        String::from(value.traced())
1169    }
1170}
1171
1172#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1173impl From<TracedDOMString> for Vec<u8> {
1174    fn from(value: TracedDOMString) -> Self {
1175        value.ensure_rust_string();
1176        let inner = value.0.take();
1177        match inner {
1178            DOMStringType::Rust(s) => s.into_bytes(),
1179            DOMStringType::JSString(_) => unreachable!(),
1180            #[cfg(test)]
1181            DOMStringType::Latin1Vec(items) => items,
1182            DOMStringType::RustStatic(_) => unreachable!(),
1183        }
1184    }
1185}
1186
1187impl From<DOMString> for Vec<u8> {
1188    fn from(value: DOMString) -> Self {
1189        Vec::<u8>::from(value.traced())
1190    }
1191}
1192
1193impl From<Cow<'_, str>> for DOMString {
1194    fn from(value: Cow<'_, str>) -> Self {
1195        TracedDOMString(RefCell::new(DOMStringType::Rust(value.into_owned()))).root()
1196    }
1197}
1198
1199impl Zeroize for TracedDOMString {
1200    fn zeroize(&mut self) {
1201        self.0.get_mut().zeroize();
1202    }
1203}
1204
1205impl Zeroize for DOMString {
1206    fn zeroize(&mut self) {
1207        self.0.zeroize()
1208    }
1209}
1210
1211#[macro_export]
1212macro_rules! match_domstring_ascii_inner {
1213    ($variant: expr, $input: expr, $ascii_literal: literal => $then: expr, $($rest:tt)*) => {
1214        if {
1215            debug_assert!(($ascii_literal).is_ascii());
1216            $ascii_literal.as_bytes()
1217        } == $input.bytes() {
1218          $then
1219        } else {
1220            $crate::match_domstring_ascii_inner!($variant, $input, $($rest)*)
1221        }
1222
1223    };
1224    ($variant: expr, $input: expr, $p: pat => $then: expr,) => {
1225        match $input {
1226            $p => $then
1227        }
1228    }
1229}
1230
1231/// <https://html.spec.whatwg.org/multipage/#valid-floating-point-number>
1232/// TODO(#39315): Use the validation rule from Stylo
1233pub fn is_valid_floating_point_number_string(s: &str) -> bool {
1234    static RE: LazyLock<Regex> =
1235        LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
1236
1237    RE.is_match(s)
1238}
1239
1240/// Use this to match &str against lazydomstring efficiently.
1241/// You are only allowed to match ascii strings otherwise this macro will
1242/// lead to wrong results. Additionally, match_domstring_ascii cannot be nested.
1243/// ```ignore
1244/// let s = DOMString::from("test");
1245/// let value = match_domstring!(s,
1246/// "test1" => 1,
1247/// "test2" => 2,
1248/// "test" => 3,
1249/// _ => 4,
1250/// );
1251/// assert_eq!(value, 3);
1252/// ```
1253///
1254/// The `RefCell` inside `DOMString` is borrowed for the duration of the `match`,
1255/// so the string cannot be accessed again inside a `match` arm.
1256#[macro_export]
1257macro_rules! match_domstring_ascii {
1258    ($input:expr, $($tail:tt)*) => {
1259        {
1260            use $crate::domstring::EncodedBytes;
1261            use js::context::NoGC;
1262
1263            // This is safe as we compare to raw strings, meaning no GC can happen while this comparison is alive.
1264            let no_gc = unsafe { NoGC::new() };
1265            let encoded_bytes = $input.encoded_bytes(&no_gc);
1266            match encoded_bytes {
1267                EncodedBytes::Latin1(_) => {
1268                    $crate::match_domstring_ascii_inner!(EncodedBytes::Latin1, encoded_bytes, $($tail)*)
1269                }
1270                EncodedBytes::Utf8(_) => {
1271                    $crate::match_domstring_ascii_inner!(EncodedBytes::Utf8, encoded_bytes, $($tail)*)
1272                }
1273
1274            }
1275        }
1276    };
1277}
1278
1279#[cfg(test)]
1280mod tests {
1281    use super::*;
1282
1283    const LATIN1_PILLCROW: u8 = 0xB6;
1284    const UTF8_PILLCROW: [u8; 2] = [194, 182];
1285    const LATIN1_POWER2: u8 = 0xB2;
1286
1287    fn from_latin1(l1vec: Vec<u8>) -> DOMString {
1288        TracedDOMString(RefCell::new(DOMStringType::Latin1Vec(l1vec))).root()
1289    }
1290
1291    #[test]
1292    fn string_functions() {
1293        let s = DOMString::from("AbBcC❤&%$#");
1294        let s_copy = s.clone();
1295        assert_eq!(s.to_ascii_lowercase(), "abbcc❤&%$#");
1296        assert_eq!(s, s_copy);
1297        assert_eq!(s.len_utf8().0, 12);
1298        assert_eq!(s_copy.len_utf8().0, 12);
1299        assert!(s.starts_with('A'));
1300        let s2 = DOMString::from("");
1301        assert!(s2.is_empty());
1302    }
1303
1304    #[test]
1305    fn string_functions_latin1() {
1306        {
1307            let s = from_latin1(vec![
1308                b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1309            ]);
1310            assert_eq!(s.to_ascii_lowercase(), "abbcc&%$#²");
1311        }
1312        {
1313            let s = from_latin1(vec![b'A', b'b', b'B', b'c', b'C']);
1314            assert_eq!(s.to_ascii_lowercase(), "abbcc");
1315        }
1316        {
1317            let s = from_latin1(vec![
1318                b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1319            ]);
1320            assert_eq!(s.len_utf8().0, 11);
1321            assert!(s.starts_with('A'));
1322        }
1323        {
1324            let s = from_latin1(vec![]);
1325            assert!(s.is_empty());
1326        }
1327    }
1328
1329    #[test]
1330    fn test_length() {
1331        let s1 = from_latin1(vec![
1332            0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,
1333            0xAE, 0xAF,
1334        ]);
1335        let s2 = from_latin1(vec![
1336            0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
1337            0xBE, 0xBF,
1338        ]);
1339        let s3 = from_latin1(vec![
1340            0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
1341            0xCE, 0xCF,
1342        ]);
1343        let s4 = from_latin1(vec![
1344            0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
1345            0xDE, 0xDF,
1346        ]);
1347        let s5 = from_latin1(vec![
1348            0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
1349            0xEE, 0xEF,
1350        ]);
1351        let s6 = from_latin1(vec![
1352            0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
1353            0xFE, 0xFF,
1354        ]);
1355
1356        let s1_utf8 = String::from("\u{00A0}¡¢£¤¥¦§¨©ª«¬\u{00AD}®¯");
1357        let s2_utf8 = String::from("°±²³´µ¶·¸¹º»¼½¾¿");
1358        let s3_utf8 = String::from("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
1359        let s4_utf8 = String::from("ÐÑÒÓÔÕÖרÙÚÛÜÝÞß");
1360        let s5_utf8 = String::from("àáâãäåæçèéêëìíîï");
1361        let s6_utf8 = String::from("ðñòóôõö÷øùúûüýþÿ");
1362
1363        assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1364        assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1365        assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1366        assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1367        assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1368        assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1369
1370        s1.ensure_rust_string();
1371        s2.ensure_rust_string();
1372        s3.ensure_rust_string();
1373        s4.ensure_rust_string();
1374        s5.ensure_rust_string();
1375        s6.ensure_rust_string();
1376        assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1377        assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1378        assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1379        assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1380        assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1381        assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1382    }
1383
1384    #[test]
1385    fn test_convert() {
1386        let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1387        s.ensure_rust_string();
1388        assert_eq!(&*s.str(), "abc%$");
1389    }
1390
1391    #[test]
1392    fn partial_eq() {
1393        let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1394        let string = String::from("abc%$");
1395        let s2 = DOMString::from(string.clone());
1396        let s3 = DOMString::from_static("abc%$");
1397        assert_eq!(s, s2);
1398        assert_eq!(s, string);
1399        assert_eq!(s, s3);
1400    }
1401
1402    #[test]
1403    fn encoded_latin1_bytes() {
1404        let original_latin1_bytes = vec![b'a', b'b', b'c', b'%', b'$', 0xB2];
1405        let dom_string = from_latin1(original_latin1_bytes.clone());
1406        let no_gc = unsafe { NoGC::new() };
1407        let string_latin1_bytes = match dom_string.encoded_bytes(&no_gc) {
1408            EncodedBytes::Latin1(bytes) => bytes,
1409            _ => unreachable!("Expected Latin1 encoded bytes"),
1410        };
1411        assert_eq!(*original_latin1_bytes, *string_latin1_bytes.1);
1412    }
1413
1414    #[test]
1415    fn testing_stringview() {
1416        let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1417
1418        assert_eq!(
1419            s.str().chars().collect::<Vec<char>>(),
1420            vec!['a', 'b', 'c', '%', '$', '²']
1421        );
1422        assert_eq!(s.str().as_bytes(), String::from("abc%$²").as_bytes());
1423    }
1424
1425    // We need to be extra careful here as two strings that have different
1426    // representation need to have the same hash.
1427    // Additionally, the interior mutability is only used for the conversion
1428    // which is forced by Hash. Hence, it is safe to have this interior mutability.
1429    #[test]
1430    fn test_hash() {
1431        use std::hash::{DefaultHasher, Hash, Hasher};
1432        fn hash_value(d: &DOMString) -> u64 {
1433            let mut hasher = DefaultHasher::new();
1434            d.hash(&mut hasher);
1435            hasher.finish()
1436        }
1437
1438        let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1439        let s_converted = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1440        s_converted.ensure_rust_string();
1441        let s2 = DOMString::from("abc%$²");
1442        let s3 = DOMString::from_static("abc%$²");
1443
1444        let hash_s = hash_value(&s);
1445        let hash_s_converted = hash_value(&s_converted);
1446        let hash_s2 = hash_value(&s2);
1447        let hash_s3 = hash_value(&s3);
1448
1449        assert_eq!(hash_s, hash_s2);
1450        assert_eq!(hash_s, hash_s_converted);
1451        assert_eq!(hash_s, hash_s3);
1452    }
1453
1454    // Testing match_lazydomstring if it executes the statements in the match correctly
1455    #[test]
1456    fn test_match_executing() {
1457        // executing
1458        {
1459            let s = from_latin1(vec![b'a', b'b', b'c']);
1460            match_domstring_ascii!( s,
1461                "abc" => assert!(true),
1462                "bcd" => assert!(false),
1463                _ =>  (),
1464            );
1465        }
1466
1467        {
1468            let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1469            match_domstring_ascii!( s,
1470                "abc/" => assert!(true),
1471                "bcd" => assert!(false),
1472                _ =>  (),
1473            );
1474        }
1475
1476        {
1477            let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1478            match_domstring_ascii!( s,
1479                "bcd" => assert!(false),
1480                "abc%$" => assert!(true),
1481                _ => (),
1482            );
1483        }
1484
1485        {
1486            let s = DOMString::from("abcde");
1487            match_domstring_ascii!( s,
1488                "abc" => assert!(false),
1489                "bcd" => assert!(false),
1490                _ => assert!(true),
1491            );
1492        }
1493        {
1494            let s = DOMString::from("abc%$");
1495            match_domstring_ascii!( s,
1496                "bcd" => assert!(false),
1497                "abc%$" => assert!(true),
1498                _ =>  (),
1499            );
1500        }
1501        {
1502            let s = from_latin1(vec![b'a', b'b', b'c']);
1503            match_domstring_ascii!( s,
1504                "abcdd" => assert!(false),
1505                "bcd" => assert!(false),
1506                _ => (),
1507            );
1508        }
1509        {
1510            let s = DOMString::from_static("abc");
1511            match_domstring_ascii!( s,
1512                "abc" => assert!(true),
1513                "bcd" => assert!(false),
1514                _ => (),
1515            );
1516        }
1517    }
1518
1519    // Testing match_lazydomstring if it evaluates to the correct expression
1520    #[test]
1521    fn test_match_returning_result() {
1522        {
1523            let s = from_latin1(vec![b'a', b'b', b'c']);
1524            let res = match_domstring_ascii!( s,
1525                "abc" => true,
1526                "bcd" => false,
1527                _ => false,
1528            );
1529            assert_eq!(res, true);
1530        }
1531        {
1532            let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1533            let res = match_domstring_ascii!( s,
1534                "abc/" => true,
1535                "bcd" => false,
1536                _ => false,
1537            );
1538            assert_eq!(res, true);
1539        }
1540        {
1541            let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1542            let res = match_domstring_ascii!( s,
1543                "bcd" => false,
1544                "abc%$" => true,
1545                _ => false,
1546            );
1547            assert_eq!(res, true);
1548        }
1549
1550        {
1551            let s = DOMString::from("abcde");
1552            let res = match_domstring_ascii!( s,
1553                "abc" => false,
1554                "bcd" => false,
1555                _ => true,
1556            );
1557            assert_eq!(res, true);
1558        }
1559        {
1560            let s = DOMString::from("abc%$");
1561            let res = match_domstring_ascii!( s,
1562                "bcd" => false,
1563                "abc%$" => true,
1564                _ => false,
1565            );
1566            assert_eq!(res, true);
1567        }
1568        {
1569            let s = from_latin1(vec![b'a', b'b', b'c']);
1570            let res = match_domstring_ascii!( s,
1571                "abcdd" => false,
1572                "bcd" => false,
1573                _ => true,
1574            );
1575            assert_eq!(res, true);
1576        }
1577    }
1578
1579    #[test]
1580    #[cfg(debug_assertions)]
1581    #[should_panic]
1582    fn test_match_panic() {
1583        let s = DOMString::from("abcd");
1584        let _res = match_domstring_ascii!(s,
1585            "❤" => true,
1586            _ => false,);
1587    }
1588
1589    #[test]
1590    #[cfg(debug_assertions)]
1591    #[should_panic]
1592    fn test_match_panic2() {
1593        let s = DOMString::from("abcd");
1594        let _res = match_domstring_ascii!(s,
1595            "abc" => false,
1596            "❤" => true,
1597            _ => false,
1598        );
1599    }
1600
1601    #[test]
1602    fn test_strip_whitespace() {
1603        {
1604            let mut s = from_latin1(vec![
1605                b' ', b' ', b' ', b'\n', b' ', b'a', b'b', b'c', b'%', b'$', 0xB2, b' ',
1606            ]);
1607
1608            s.strip_leading_and_trailing_ascii_whitespace();
1609            s.ensure_rust_string();
1610            assert_eq!(&*s.str(), "abc%$²");
1611        }
1612        {
1613            let mut s = DOMString::from("   \n  abc%$ ");
1614
1615            s.strip_leading_and_trailing_ascii_whitespace();
1616            s.ensure_rust_string();
1617            assert_eq!(&*s.str(), "abc%$");
1618        }
1619        {
1620            let mut s = DOMString::from_static("   \n  abc%$ ");
1621
1622            s.strip_leading_and_trailing_ascii_whitespace();
1623            s.ensure_rust_string();
1624            assert_eq!(&*s.str(), "abc%$");
1625        }
1626    }
1627
1628    // https://infra.spec.whatwg.org/#ascii-whitespace
1629    #[test]
1630    fn contains_html_space_characters() {
1631        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_TAB, b'a', b'a']); // TAB
1632        assert!(s.contains_html_space_characters());
1633        s.ensure_rust_string();
1634        assert!(s.contains_html_space_characters());
1635
1636        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_NEWLINE, b'a', b'a']); // NEWLINE
1637        assert!(s.contains_html_space_characters());
1638        s.ensure_rust_string();
1639        assert!(s.contains_html_space_characters());
1640
1641        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_FORMFEED, b'a', b'a']); // FF
1642        assert!(s.contains_html_space_characters());
1643        s.ensure_rust_string();
1644        assert!(s.contains_html_space_characters());
1645
1646        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_CR, b'a', b'a']); // Carriage Return
1647        assert!(s.contains_html_space_characters());
1648        s.ensure_rust_string();
1649        assert!(s.contains_html_space_characters());
1650
1651        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']); // SPACE
1652        assert!(s.contains_html_space_characters());
1653        s.ensure_rust_string();
1654        assert!(s.contains_html_space_characters());
1655
1656        let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'a']);
1657        assert!(!s.contains_html_space_characters());
1658        s.ensure_rust_string();
1659        assert!(!s.contains_html_space_characters());
1660
1661        let s = DOMString::from_static("aba aaa");
1662        assert!(s.contains_html_space_characters());
1663        s.ensure_rust_string();
1664        assert!(s.contains_html_space_characters());
1665    }
1666
1667    #[test]
1668    fn atom() {
1669        let s = from_latin1(vec![b'a', b'a', b'a', 0x20, b'a', b'a']);
1670        let atom1 = Atom::from(s);
1671        let s2 = DOMString::from("aaa aa");
1672        let atom2 = Atom::from(s2);
1673        assert_eq!(atom1, atom2);
1674        let s3 = from_latin1(vec![b'a', b'a', b'a', 0xB2, b'a', b'a']);
1675        let atom3 = Atom::from(s3);
1676        assert_ne!(atom1, atom3);
1677        let s3 = DOMString::from_static("aaa\u{03B1}aa");
1678        let atom3 = Atom::from(s3);
1679        assert_ne!(atom1, atom3);
1680        let s4 = DOMString::from_static("aaa aa");
1681        let atom4 = Atom::from(s4);
1682        assert_eq!(atom2, atom4);
1683    }
1684
1685    #[test]
1686    fn namespace() {
1687        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1688        let atom1 = Namespace::from(s);
1689        let s2 = DOMString::from("aaa aa");
1690        let atom2 = Namespace::from(s2);
1691        assert_eq!(atom1, atom2);
1692        let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1693        let atom3 = Namespace::from(s3);
1694        assert_ne!(atom1, atom3);
1695        let s4 = DOMString::from_static("aaa aa");
1696        let atom4 = Namespace::from(s4);
1697        assert_eq!(atom2, atom4);
1698    }
1699
1700    #[test]
1701    fn localname() {
1702        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1703        let atom1 = LocalName::from(s);
1704        let s2 = DOMString::from("aaa aa");
1705        let atom2 = LocalName::from(s2);
1706        assert_eq!(atom1, atom2);
1707        let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1708        let atom3 = LocalName::from(s3);
1709        assert_ne!(atom1, atom3);
1710        let s4 = DOMString::from_static("aaa aa");
1711        let atom4 = LocalName::from(s4);
1712        assert_eq!(atom2, atom4);
1713    }
1714
1715    #[test]
1716    fn is_ascii_lowercase() {
1717        let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1718        assert!(!s.is_ascii_lowercase());
1719        let s = from_latin1(vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a']);
1720        assert!(!s.is_ascii_lowercase());
1721        let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'z']);
1722        assert!(s.is_ascii_lowercase());
1723        let s = from_latin1(vec![b'`', b'a', b'a', b'a', b'z']);
1724        assert!(!s.is_ascii_lowercase());
1725        let s = DOMString::from("`aaaz");
1726        assert!(!s.is_ascii_lowercase());
1727        let s = DOMString::from("aaaz");
1728        assert!(s.is_ascii_lowercase());
1729        let s = DOMString::from_static("aaaz");
1730        assert!(s.is_ascii_lowercase());
1731    }
1732
1733    #[test]
1734    fn test_as_bytes() {
1735        const ASCII_SMALL_A: u8 = b'a';
1736        const ASCII_SMALL_Z: u8 = b'z';
1737
1738        let v1 = vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a'];
1739        let s = from_latin1(v1.clone());
1740        {
1741            let no_gc = unsafe { NoGC::new() };
1742            assert_eq!(
1743                *s.as_bytes(&no_gc),
1744                [
1745                    ASCII_SMALL_A,
1746                    ASCII_SMALL_A,
1747                    ASCII_SMALL_A,
1748                    UTF8_PILLCROW[0],
1749                    UTF8_PILLCROW[1],
1750                    ASCII_SMALL_A,
1751                    ASCII_SMALL_A
1752                ]
1753            );
1754        }
1755        let v2 = vec![b'a', b'a', b'a', b'a', b'z'];
1756        let s = from_latin1(v2.clone());
1757        {
1758            let no_gc = unsafe { NoGC::new() };
1759            assert_eq!(
1760                *s.as_bytes(&no_gc),
1761                [
1762                    ASCII_SMALL_A,
1763                    ASCII_SMALL_A,
1764                    ASCII_SMALL_A,
1765                    ASCII_SMALL_A,
1766                    ASCII_SMALL_Z
1767                ]
1768            );
1769        }
1770
1771        let str = "abc%$²".to_owned();
1772        let s = DOMString::from(str.clone());
1773        {
1774            let no_gc = unsafe { NoGC::new() };
1775            assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1776        }
1777        let str = "AbBcC❤&%$#".to_owned();
1778        let s = DOMString::from(str.clone());
1779        {
1780            let no_gc = unsafe { NoGC::new() };
1781            assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1782        }
1783        let s = DOMString::from_static("AbBcC❤&%$#");
1784
1785        {
1786            let no_gc = unsafe { NoGC::new() };
1787            assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1788        }
1789    }
1790}