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