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