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