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