1#![allow(clippy::non_canonical_partial_ord_impl)]
6use std::borrow::{Cow, ToOwned};
7use std::cell::{Ref, RefCell, RefMut};
8use std::default::Default;
9use std::ops::{Deref, DerefMut};
10use std::ptr::{self, NonNull};
11use std::str::FromStr;
12use std::sync::LazyLock;
13use std::{fmt, slice, str};
14
15use html5ever::{LocalName, Namespace};
16use js::context::{JSContext, NoGC};
17use js::conversions::{ToJSValConvertible, jsstr_to_string};
18use js::gc::{HandleValue, MutableHandleValue};
19use js::jsapi::{Heap, JS_GetLatin1StringCharsAndLength, JSString};
20use js::jsval::StringValue;
21use js::rust::{Runtime, Trace};
22use malloc_size_of::MallocSizeOfOps;
23use num_traits::{ToPrimitive, Zero};
24use regex::Regex;
25use servo_base::text::{AssumeUnder4GB, Utf8CodeUnits, Utf16CodeUnits};
26use style::Atom;
27use style::str::HTML_SPACE_CHARACTERS;
28use zeroize::Zeroize;
29
30use crate::trace::RootedTraceableBox;
31
32const ASCII_END: u8 = 0x7E;
33const ASCII_CAPITAL_A: u8 = 0x41;
34const ASCII_CAPITAL_Z: u8 = 0x5A;
35const ASCII_LOWERCASE_A: u8 = 0x61;
36const ASCII_LOWERCASE_Z: u8 = 0x7A;
37const ASCII_TAB: u8 = 0x09;
38const ASCII_NEWLINE: u8 = 0x0A;
39const ASCII_FORMFEED: u8 = 0x0C;
40const ASCII_CR: u8 = 0x0D;
41const ASCII_SPACE: u8 = 0x20;
42
43unsafe fn get_latin1_string_bytes(string: &Heap<*mut JSString>) -> &[u8] {
54 debug_assert!(!string.get().is_null());
55 let mut length = 0;
56 unsafe {
57 let chars = JS_GetLatin1StringCharsAndLength(
58 Runtime::get().expect("JS runtime has shut down").as_ptr(),
59 ptr::null(),
60 string.get(),
61 &mut length,
62 );
63 assert!(!chars.is_null());
64 slice::from_raw_parts(chars, length)
65 }
66}
67
68pub enum EncodedBytes<'a> {
70 Latin1((&'a NoGC, Ref<'a, [u8]>)),
72 Utf8((&'a NoGC, Ref<'a, [u8]>)),
74}
75
76impl EncodedBytes<'_> {
77 pub fn bytes(&self) -> &[u8] {
80 match self {
81 Self::Latin1(bytes) => &bytes.1,
82 Self::Utf8(bytes) => &bytes.1,
83 }
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.bytes().is_empty()
89 }
90}
91
92#[derive(Clone, Debug)]
93enum AtomTypes {
94 LocalName(LocalName),
95 Stylo(stylo_atoms::Atom),
96 Namespace(web_atoms::Namespace),
97 Prefix(web_atoms::Prefix),
98}
99
100impl From<LocalName> for AtomTypes {
101 fn from(value: LocalName) -> Self {
102 AtomTypes::LocalName(value)
103 }
104}
105
106impl From<stylo_atoms::Atom> for AtomTypes {
107 fn from(value: stylo_atoms::Atom) -> Self {
108 AtomTypes::Stylo(value)
109 }
110}
111
112impl From<web_atoms::Namespace> for AtomTypes {
113 fn from(value: web_atoms::Namespace) -> Self {
114 AtomTypes::Namespace(value)
115 }
116}
117
118impl From<web_atoms::Prefix> for AtomTypes {
119 fn from(value: web_atoms::Prefix) -> Self {
120 AtomTypes::Prefix(value)
121 }
122}
123
124impl AsRef<str> for AtomTypes {
125 fn as_ref(&self) -> &str {
126 match self {
127 AtomTypes::LocalName(atom) => atom.as_str(),
128 AtomTypes::Stylo(atom) => atom.as_str(),
129 AtomTypes::Namespace(atom) => atom.as_str(),
130 AtomTypes::Prefix(atom) => atom.as_str(),
131 }
132 }
133}
134
135#[derive(Zeroize)]
136#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
137enum DOMStringType {
138 Rust(String),
140 #[zeroize(skip)]
142 JSString(Box<Heap<*mut JSString>>),
143 #[cfg(test)]
144 Latin1Vec(Vec<u8>),
147 #[zeroize(skip)] RustStatic(&'static str),
149 #[zeroize(skip)]
150 Atom(AtomTypes),
151}
152
153impl Clone for DOMStringType {
154 fn clone(&self) -> Self {
155 match self {
156 Self::Rust(arg0) => Self::Rust(arg0.clone()),
157 Self::JSString(rooted_box) => Self::JSString(Heap::boxed(rooted_box.get())),
158 #[cfg(test)]
159 Self::Latin1Vec(arg0) => Self::Latin1Vec(arg0.clone()),
160 Self::RustStatic(arg0) => Self::RustStatic(arg0),
161 Self::Atom(atom) => Self::Atom(atom.clone()),
162 }
163 }
164}
165
166impl Default for DOMStringType {
167 fn default() -> Self {
168 Self::Rust(Default::default())
169 }
170}
171
172impl DOMStringType {
173 unsafe fn as_raw_bytes(&self) -> &[u8] {
183 match self {
184 DOMStringType::Rust(s) => s.as_bytes(),
185 DOMStringType::JSString(rooted_traceable_box) => unsafe {
186 get_latin1_string_bytes(rooted_traceable_box)
187 },
188 #[cfg(test)]
189 DOMStringType::Latin1Vec(items) => items,
190 DOMStringType::RustStatic(s) => s.as_bytes(),
191 DOMStringType::Atom(s) => s.as_ref().as_bytes(),
192 }
193 }
194
195 fn ensure_rust_string(&mut self) -> &mut String {
196 let new_string = match self {
197 DOMStringType::Rust(string) => return string,
198 DOMStringType::JSString(rooted_traceable_box) => {
199 let cx = unsafe { JSContext::get_from_thread() };
200 let cx = cx.as_ref().expect("JS runtime has shut down");
201 unsafe { jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap()) }
202 },
203 #[cfg(test)]
204 DOMStringType::Latin1Vec(items) => {
205 let mut v = vec![0; items.len() * 2];
206 let real_size =
207 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
208 v.truncate(real_size);
209
210 unsafe { String::from_utf8_unchecked(v) }
213 },
214 DOMStringType::RustStatic(s) => s.to_owned(),
216 DOMStringType::Atom(s) => s.as_ref().to_owned(),
217 };
218 *self = DOMStringType::Rust(new_string);
219 self.ensure_rust_string()
220 }
221}
222
223#[derive(Debug)]
226pub struct StringView<'a>(Ref<'a, str>);
227
228impl StringView<'_> {
229 pub fn split_html_space_characters(&self) -> impl Iterator<Item = &str> {
230 self.split(HTML_SPACE_CHARACTERS)
231 .filter(|string| !string.is_empty())
232 }
233}
234
235impl From<StringView<'_>> for String {
236 fn from(string_view: StringView<'_>) -> Self {
237 string_view.0.to_string()
238 }
239}
240
241impl Deref for StringView<'_> {
242 type Target = str;
243 fn deref(&self) -> &str {
244 &(self.0)
245 }
246}
247
248impl AsRef<str> for StringView<'_> {
249 fn as_ref(&self) -> &str {
250 &(self.0)
251 }
252}
253
254impl PartialEq for StringView<'_> {
255 fn eq(&self, other: &Self) -> bool {
256 self.0.eq(&*(other.0))
257 }
258}
259
260impl PartialEq<&str> for StringView<'_> {
261 fn eq(&self, other: &&str) -> bool {
262 self.0.eq(*other)
263 }
264}
265
266impl Eq for StringView<'_> {}
267
268impl PartialOrd for StringView<'_> {
269 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
270 self.0.partial_cmp(&**other)
271 }
272}
273
274impl Ord for StringView<'_> {
275 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
276 self.0.cmp(other)
277 }
278}
279
280unsafe impl Trace for DOMStringType {
286 unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
287 unsafe {
288 match self {
289 DOMStringType::Rust(_s) => {},
290 DOMStringType::JSString(rooted_traceable_box) => rooted_traceable_box.trace(tracer),
291 #[cfg(test)]
292 DOMStringType::Latin1Vec(_s) => {},
293 DOMStringType::RustStatic(_) => {},
294 DOMStringType::Atom(_) => {},
295 }
296 }
297 }
298}
299
300impl malloc_size_of::MallocSizeOf for DOMStringType {
301 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
302 match self {
303 DOMStringType::Rust(s) => s.size_of(ops),
304 DOMStringType::JSString(_rooted_traceable_box) => {
305 0
307 },
308 #[cfg(test)]
309 DOMStringType::Latin1Vec(s) => s.size_of(ops),
310 DOMStringType::RustStatic(_s) => 0,
311 DOMStringType::Atom(_) => 0,
312 }
313 }
314}
315
316impl std::fmt::Debug for DOMStringType {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 match self {
319 DOMStringType::Rust(s) => f.debug_struct("DOMString").field("rust_string", s).finish(),
320 DOMStringType::JSString(_rooted_traceable_box) => f.debug_struct("DOMString").finish(),
321 #[cfg(test)]
322 DOMStringType::Latin1Vec(s) => f
323 .debug_struct("DOMString")
324 .field("latin1_string", s)
325 .finish(),
326 DOMStringType::RustStatic(s) => f
327 .debug_struct("DOMString")
328 .field("static_string", s)
329 .finish(),
330 DOMStringType::Atom(s) => f.debug_struct("DOMString").field("atom", s).finish(),
331 }
332 }
333}
334
335#[repr(transparent)]
373#[derive(Clone, Debug, Default, MallocSizeOf, JSTraceable)]
374#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
375pub struct TracedDOMString(RefCell<DOMStringType>);
376
377#[derive(Default, MallocSizeOf, JSTraceable)]
379pub struct DOMString(RootedTraceableBox<TracedDOMString>);
380
381impl std::fmt::Debug for DOMString {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 f.debug_tuple("DOMString").field(&*self.0).finish()
384 }
385}
386
387impl DerefMut for DOMString {
388 fn deref_mut(&mut self) -> &mut Self::Target {
389 &mut self.0
390 }
391}
392
393impl Deref for DOMString {
394 type Target = TracedDOMString;
395
396 fn deref(&self) -> &Self::Target {
397 &self.0
398 }
399}
400
401impl Clone for DOMString {
402 fn clone(&self) -> Self {
403 self.0.clone().root()
404 }
405}
406
407impl DOMString {
408 pub fn new() -> DOMString {
410 TracedDOMString::default().root()
411 }
412
413 pub fn from_js_string(
417 cx: &mut JSContext,
418 value: HandleValue,
419 ) -> Result<DOMString, DOMStringErrorType> {
420 rooted!(&in(cx) let string_ptr = unsafe { js::rust::ToString(cx, value) });
431 unsafe { DOMString::from_raw_js_string(cx, *string_ptr) }
432 }
433
434 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
441 pub unsafe fn from_raw_js_string(
442 cx: &JSContext,
443 string_ptr: *mut JSString,
444 ) -> Result<DOMString, DOMStringErrorType> {
445 if string_ptr.is_null() {
446 debug!("ToString failed");
447 Err(DOMStringErrorType::JSConversionError)
448 } else {
449 let latin1 = unsafe { js::jsapi::JS_DeprecatedStringHasLatin1Chars(string_ptr) };
450 let inner = if latin1 {
451 let h = Heap::boxed(string_ptr);
452 DOMStringType::JSString(h)
453 } else {
454 DOMStringType::Rust(unsafe {
456 jsstr_to_string(cx, NonNull::new(string_ptr).unwrap())
457 })
458 };
459 Ok(TracedDOMString(RefCell::new(inner)).root())
460 }
461 }
462
463 pub fn from_static(s: &'static str) -> DOMString {
465 TracedDOMString(RefCell::new(DOMStringType::RustStatic(s))).root()
466 }
467
468 pub fn traced(self) -> TracedDOMString {
469 *self.0.into_box()
470 }
471
472 pub fn str(&self) -> StringView<'_> {
473 self.0.str()
474 }
475}
476
477#[derive(Debug)]
478pub enum DOMStringErrorType {
479 JSConversionError,
480}
481
482impl From<String> for TracedDOMString {
483 fn from(string: String) -> Self {
484 TracedDOMString(RefCell::new(DOMStringType::Rust(string)))
485 }
486}
487
488impl TracedDOMString {
489 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
490 pub fn root(self) -> DOMString {
491 DOMString(RootedTraceableBox::from_box(Box::new(self)))
492 }
493
494 fn ensure_rust_string(&self) -> RefMut<'_, String> {
497 let inner = self.0.borrow_mut();
498 RefMut::map(inner, |inner| inner.ensure_rust_string())
499 }
500
501 #[expect(unused)]
503 fn debug_js(&self, cx: &JSContext) {
504 match *self.0.borrow() {
505 DOMStringType::Rust(ref s) => info!("Rust String ({})", s),
506 DOMStringType::JSString(ref rooted_traceable_box) => {
507 let s = unsafe {
508 jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap())
509 };
510 info!("JSString ({})", s);
511 },
512 #[cfg(test)]
513 DOMStringType::Latin1Vec(ref items) => info!("Latin1 string"),
514 DOMStringType::RustStatic(s) => info!("Static Rust String ({})", s),
515 DOMStringType::Atom(ref s) => info!("Atom String ({:?})", s),
516 }
517 }
518
519 pub fn str(&self) -> StringView<'_> {
521 {
522 let inner = self.0.borrow();
523 if matches!(&*inner, DOMStringType::Rust(..)) {
524 return StringView(Ref::map(inner, |inner| match inner {
525 DOMStringType::Rust(string) => string.as_str(),
526 _ => unreachable!("Guaranteed by condition above"),
527 }));
528 }
529 }
530
531 self.ensure_rust_string();
532 self.str()
533 }
534
535 pub fn encoded_bytes<'a>(&'a self, no_gc: &'a NoGC) -> EncodedBytes<'a> {
538 let inner = self.0.borrow();
539 match &*inner {
541 DOMStringType::Rust(..) | DOMStringType::RustStatic(..) => EncodedBytes::Utf8((
542 no_gc,
543 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
544 )),
545 DOMStringType::JSString(..) => EncodedBytes::Latin1((
546 no_gc,
547 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
548 )),
549 #[cfg(test)]
550 DOMStringType::Latin1Vec(..) => EncodedBytes::Latin1((
551 no_gc,
552 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
553 )),
554 DOMStringType::Atom(_) => EncodedBytes::Utf8((
555 no_gc,
556 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
557 )),
558 }
559 }
560
561 pub fn clear(&mut self) {
562 let mut inner = self.0.borrow_mut();
563 let DOMStringType::Rust(string) = &mut *inner else {
564 *inner = DOMStringType::Rust(String::new());
565 return;
566 };
567 string.clear();
568 }
569
570 pub fn is_empty(&self) -> bool {
571 let no_gc = unsafe { NoGC::new() };
573 self.encoded_bytes(&no_gc).is_empty()
574 }
575
576 pub fn len_utf8(&self) -> Utf8CodeUnits {
583 let no_gc = unsafe { NoGC::new() };
586 Utf8CodeUnits(match self.encoded_bytes(&no_gc) {
587 EncodedBytes::Utf8(bytes) => bytes.1.len() as u32,
588 EncodedBytes::Latin1(bytes) => bytes
589 .1
590 .iter()
591 .map(|&byte| if byte < 128 { 1 } else { 2 })
594 .sum::<u32>(),
595 })
596 }
597
598 pub fn len_utf8_or_latin1(&self) -> usize {
600 let no_gc = unsafe { NoGC::new() };
602 match self.encoded_bytes(&no_gc) {
603 EncodedBytes::Utf8(bytes) => bytes.1.len(),
604 EncodedBytes::Latin1(bytes) => bytes.1.len(),
605 }
606 }
607
608 pub fn len_utf16(&self) -> Utf16CodeUnits {
613 let no_gc = unsafe { NoGC::new() };
615 match self.encoded_bytes(&no_gc) {
616 EncodedBytes::Latin1(bytes) => Utf16CodeUnits(bytes.1.len() as u32),
618 EncodedBytes::Utf8(bytes) => Utf16CodeUnits::length_of(AssumeUnder4GB, unsafe {
621 str::from_utf8_unchecked(&bytes.1)
622 }),
623 }
624 }
625
626 pub fn make_ascii_lowercase(&mut self) {
629 self.0
630 .borrow_mut()
631 .ensure_rust_string()
632 .make_ascii_lowercase();
633 }
634
635 pub fn push_str(&mut self, string_to_push: &str) {
636 self.0
637 .borrow_mut()
638 .ensure_rust_string()
639 .push_str(string_to_push);
640 }
641
642 pub fn strip_leading_and_trailing_ascii_whitespace(&mut self) {
644 if self.is_empty() {
645 return;
646 }
647
648 let mut inner = self.0.borrow_mut();
649 let string = inner.ensure_rust_string();
650 let trailing_whitespace_len = string
651 .trim_end_matches(|character: char| character.is_ascii_whitespace())
652 .len();
653 string.truncate(trailing_whitespace_len);
654 if string.is_empty() {
655 return;
656 }
657
658 let first_non_whitespace = string
659 .find(|character: char| !character.is_ascii_whitespace())
660 .unwrap();
661 string.replace_range(0..first_non_whitespace, "");
662 }
663
664 pub fn is_valid_floating_point_number_string(&self) -> bool {
666 is_valid_floating_point_number_string(self.0.borrow_mut().ensure_rust_string()) &&
667 self.parse_floating_point_number().is_some()
668 }
669
670 pub fn parse<T: FromStr>(&self) -> Result<T, <T as FromStr>::Err> {
671 self.str().parse::<T>()
672 }
673
674 pub fn parse_floating_point_number(&self) -> Option<f64> {
676 parse_floating_point_number(&self.str())
677 }
678
679 pub fn set_best_representation_of_the_floating_point_number(&mut self) {
681 if let Some(val) = self.parse_floating_point_number() {
682 let parsed_value = if val.is_zero() { 0.0_f64 } else { val };
684
685 *self.0.borrow_mut() = DOMStringType::Rust(parsed_value.to_string());
686 }
687 }
688
689 pub fn to_lowercase(&self) -> String {
690 self.str().to_lowercase()
691 }
692
693 pub fn to_uppercase(&self) -> String {
694 self.str().to_uppercase()
695 }
696
697 pub fn strip_newlines(&mut self) {
698 self.0
701 .borrow_mut()
702 .ensure_rust_string()
703 .retain(|character| character != '\r' && character != '\n');
704 }
705
706 pub fn normalize_newlines(&mut self) {
708 let mut inner = self.0.borrow_mut();
712 let string = inner.ensure_rust_string();
713 *string = string.replace("\r\n", "\n").replace("\r", "\n")
714 }
715
716 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
717 pub fn replace(self, needle: &str, replace_char: &str) -> DOMString {
718 let new_string = self.str().to_owned();
719 TracedDOMString(RefCell::new(DOMStringType::Rust(
720 new_string.replace(needle, replace_char),
721 )))
722 .root()
723 }
724
725 pub fn starts_with(&self, c: char) -> bool {
727 if !c.is_ascii() {
728 self.str().starts_with(c)
729 } else {
730 let no_gc = unsafe { NoGC::new() };
734 self.encoded_bytes(&no_gc).bytes().starts_with(&[c as u8])
735 }
736 }
737
738 pub fn starts_with_str(&self, needle: &str) -> bool {
739 self.str().starts_with(needle)
740 }
741
742 pub fn ends_with_str(&self, needle: &str) -> bool {
743 self.str().ends_with(needle)
744 }
745
746 pub fn contains(&self, needle: &str) -> bool {
747 self.str().contains(needle)
748 }
749
750 pub fn eq_ignore_ascii_case(&self, other: &str) -> bool {
755 if other.is_ascii() {
756 let no_gc = unsafe { NoGC::new() };
758 self.encoded_bytes(&no_gc)
759 .bytes()
760 .eq_ignore_ascii_case(other.as_bytes())
761 } else {
762 self.str().eq_ignore_ascii_case(other)
763 }
764 }
765
766 pub fn to_ascii_lowercase(&self) -> String {
767 let no_gc = unsafe { NoGC::new() };
769 let conversion = match self.encoded_bytes(&no_gc) {
770 EncodedBytes::Latin1(bytes) => {
771 if bytes.1.iter().all(|c| *c <= ASCII_END) {
772 Some(unsafe {
774 String::from_utf8_unchecked(
775 bytes
776 .1
777 .iter()
778 .map(|c| {
779 if *c >= ASCII_CAPITAL_A && *c <= ASCII_CAPITAL_Z {
780 c + 32
781 } else {
782 *c
783 }
784 })
785 .collect(),
786 )
787 })
788 } else {
789 None
790 }
791 },
792 EncodedBytes::Utf8(bytes) => unsafe {
793 Some(str::from_utf8_unchecked(&bytes.1).to_ascii_lowercase())
795 },
796 };
797 if let Some(conversion) = conversion {
799 conversion
800 } else {
801 self.str().to_ascii_lowercase()
802 }
803 }
804
805 fn contains_space_characters(
806 &self,
807 latin1_characters: &'static [u8],
808 utf8_characters: &'static [char],
809 ) -> bool {
810 let no_gc = unsafe { NoGC::new() };
812 match self.encoded_bytes(&no_gc) {
813 EncodedBytes::Latin1(items) => {
814 latin1_characters.iter().any(|byte| items.1.contains(byte))
815 },
816 EncodedBytes::Utf8(bytes) => {
817 let s = unsafe { str::from_utf8_unchecked(&bytes.1) };
819 s.contains(utf8_characters)
820 },
821 }
822 }
823
824 pub fn contains_tab_or_newline(&self) -> bool {
826 const LATIN_TAB_OR_NEWLINE: [u8; 3] = [ASCII_TAB, ASCII_NEWLINE, ASCII_CR];
827 const UTF8_TAB_OR_NEWLINE: [char; 3] = ['\u{0009}', '\u{000a}', '\u{000d}'];
828
829 self.contains_space_characters(&LATIN_TAB_OR_NEWLINE, &UTF8_TAB_OR_NEWLINE)
830 }
831
832 pub fn contains_html_space_characters(&self) -> bool {
834 const SPACE_BYTES: [u8; 5] = [
835 ASCII_TAB,
836 ASCII_NEWLINE,
837 ASCII_FORMFEED,
838 ASCII_CR,
839 ASCII_SPACE,
840 ];
841 self.contains_space_characters(&SPACE_BYTES, HTML_SPACE_CHARACTERS)
842 }
843
844 pub fn as_bytes<'a>(&'a self, no_gc: &'a NoGC) -> BytesView<'a> {
846 if self.is_ascii() {
850 BytesView {
851 _no_gc: no_gc,
852 inner: self.0.borrow(),
853 }
854 } else {
855 self.ensure_rust_string();
856 BytesView {
857 _no_gc: no_gc,
858 inner: self.0.borrow(),
859 }
860 }
861 }
862
863 pub fn is_ascii_lowercase(&self) -> bool {
865 let no_gc = unsafe { NoGC::new() };
867 match self.encoded_bytes(&no_gc) {
868 EncodedBytes::Latin1(items) => items
869 .1
870 .iter()
871 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(c)),
872 EncodedBytes::Utf8(bytes) => bytes
873 .1
874 .iter()
875 .map(|c| c.to_u8().unwrap_or(ASCII_LOWERCASE_A - 1))
876 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(&c)),
877 }
878 }
879
880 pub fn is_ascii(&self) -> bool {
882 let no_gc = unsafe { NoGC::new() };
884 self.encoded_bytes(&no_gc).bytes().is_ascii()
885 }
886
887 pub fn is_valid_for_cookie(&self) -> bool {
891 let no_gc = unsafe { NoGC::new() };
893 match self.encoded_bytes(&no_gc) {
894 EncodedBytes::Latin1(items) | EncodedBytes::Utf8(items) => !items
895 .1
896 .iter()
897 .any(|c| *c == 0x7f || (*c <= 0x1f && *c != 0x09)),
898 }
899 }
900
901 fn with_str_reference<Result>(&self, callback: fn(&str) -> Result) -> Result {
904 let no_gc = unsafe { NoGC::new() };
907 match self.encoded_bytes(&no_gc) {
908 EncodedBytes::Latin1(latin1_bytes) => {
910 if latin1_bytes.1.iter().all(|character| character.is_ascii()) {
911 return callback(unsafe { str::from_utf8_unchecked(&latin1_bytes.1) });
914 }
915 },
916 EncodedBytes::Utf8(utf8_bytes) => {
917 return callback(unsafe { str::from_utf8_unchecked(&utf8_bytes.1) });
920 },
921 };
922 callback(self.str().deref())
923 }
924
925 pub fn normalize_crlf(&self) -> String {
933 let s = self.str();
934 let mut buf = String::new();
935 let mut prev = ' ';
936 for ch in s.chars() {
937 match ch {
938 '\n' if prev != '\r' => {
939 buf.push('\r');
940 buf.push('\n');
941 },
942 '\n' => {
943 buf.push('\n');
944 },
945 _ if prev == '\r' => {
948 buf.push('\n');
949 buf.push(ch);
950 },
951 _ => buf.push(ch),
952 };
953 prev = ch;
954 }
955 if prev == '\r' {
957 buf.push('\n');
958 }
959 buf
960 }
961}
962
963pub fn parse_floating_point_number(input: &str) -> Option<f64> {
965 input.trim().parse::<f64>().ok().filter(|value| {
971 !(value.is_infinite() || value.is_nan() || input.ends_with('.') || input.starts_with('+'))
974 })
975}
976
977pub struct BytesView<'a> {
978 _no_gc: &'a NoGC,
979 inner: Ref<'a, DOMStringType>,
980}
981
982impl Deref for BytesView<'_> {
983 type Target = [u8];
984
985 fn deref(&self) -> &Self::Target {
986 unsafe { self.inner.as_raw_bytes() }
988 }
989}
990
991impl Ord for TracedDOMString {
992 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
993 self.str().cmp(&other.str())
994 }
995}
996
997impl PartialOrd for TracedDOMString {
998 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
999 self.str().partial_cmp(&other.str())
1000 }
1001}
1002
1003impl PartialOrd for DOMString {
1004 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1005 self.0.partial_cmp(&other.0)
1006 }
1007}
1008
1009impl Extend<char> for TracedDOMString {
1010 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
1011 self.0.borrow_mut().ensure_rust_string().extend(iter)
1012 }
1013}
1014
1015impl ToJSValConvertible for TracedDOMString {
1016 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
1017 let val = self.0.borrow();
1018 match *val {
1019 DOMStringType::Rust(ref s) => s.to_jsval(cx, rval),
1020 DOMStringType::JSString(ref rooted_traceable_box) => unsafe {
1021 rval.set(StringValue(&*rooted_traceable_box.get()));
1022 },
1023 #[cfg(test)]
1024 DOMStringType::Latin1Vec(ref items) => {
1025 let mut v = vec![0; items.len() * 2];
1026 let real_size =
1027 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
1028 v.truncate(real_size);
1029
1030 String::from_utf8(v)
1031 .expect("Error in constructin test string")
1032 .to_jsval(cx, rval);
1033 },
1034 DOMStringType::RustStatic(s) => s.to_jsval(cx, rval),
1035 DOMStringType::Atom(ref atom) => atom.as_ref().to_jsval(cx, rval),
1036 };
1037 }
1038}
1039
1040impl ToJSValConvertible for DOMString {
1041 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
1042 self.0.to_jsval(cx, rval);
1043 }
1044}
1045
1046impl std::hash::Hash for TracedDOMString {
1047 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1048 self.str().hash(state);
1049 }
1050}
1051
1052impl std::hash::Hash for DOMString {
1053 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1054 self.str().hash(state);
1055 }
1056}
1057
1058impl std::fmt::Display for TracedDOMString {
1059 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1060 fmt::Display::fmt(self.str().deref(), f)
1061 }
1062}
1063
1064impl std::fmt::Display for DOMString {
1065 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066 fmt::Display::fmt(self.str().deref(), f)
1067 }
1068}
1069
1070impl std::cmp::PartialEq<str> for TracedDOMString {
1071 fn eq(&self, other: &str) -> bool {
1072 if other.is_ascii() {
1073 let no_gc = unsafe { NoGC::new() };
1075 *other.as_bytes() == *self.encoded_bytes(&no_gc).bytes()
1076 } else {
1077 self.str().deref() == other
1078 }
1079 }
1080}
1081
1082impl std::cmp::PartialEq<str> for DOMString {
1083 fn eq(&self, other: &str) -> bool {
1084 self.0.eq(other)
1085 }
1086}
1087
1088impl std::cmp::PartialEq<&str> for TracedDOMString {
1089 fn eq(&self, other: &&str) -> bool {
1090 self.eq(*other)
1091 }
1092}
1093
1094impl std::cmp::PartialEq<&str> for DOMString {
1095 fn eq(&self, other: &&str) -> bool {
1096 self.0.eq(*other)
1097 }
1098}
1099
1100impl std::cmp::PartialEq<String> for TracedDOMString {
1101 fn eq(&self, other: &String) -> bool {
1102 self.eq(other.as_str())
1103 }
1104}
1105
1106impl std::cmp::PartialEq<String> for DOMString {
1107 fn eq(&self, other: &String) -> bool {
1108 self.0.eq(other.as_str())
1109 }
1110}
1111
1112impl std::cmp::PartialEq<TracedDOMString> for String {
1113 fn eq(&self, other: &TracedDOMString) -> bool {
1114 other.eq(self)
1115 }
1116}
1117
1118impl std::cmp::PartialEq<DOMString> for String {
1119 fn eq(&self, other: &DOMString) -> bool {
1120 other.0.eq(self)
1121 }
1122}
1123
1124impl std::cmp::PartialEq<TracedDOMString> for str {
1125 fn eq(&self, other: &TracedDOMString) -> bool {
1126 other.eq(self)
1127 }
1128}
1129
1130impl std::cmp::PartialEq<DOMString> for str {
1131 fn eq(&self, other: &DOMString) -> bool {
1132 other.0.eq(self)
1133 }
1134}
1135
1136impl std::cmp::PartialEq for TracedDOMString {
1137 fn eq(&self, other: &TracedDOMString) -> bool {
1138 let no_gc = unsafe { NoGC::new() };
1139 let result = match (self.encoded_bytes(&no_gc), other.encoded_bytes(&no_gc)) {
1140 (EncodedBytes::Latin1(bytes), EncodedBytes::Latin1(other_bytes)) => {
1141 Some(*bytes.1 == *other_bytes.1)
1142 },
1143 (EncodedBytes::Latin1(bytes), EncodedBytes::Utf8(other_bytes))
1144 if other_bytes.1.is_ascii() =>
1145 {
1146 Some(*bytes.1 == *other_bytes.1)
1147 },
1148 (EncodedBytes::Utf8(bytes), EncodedBytes::Latin1(other_bytes))
1149 if bytes.1.is_ascii() =>
1150 {
1151 Some(*bytes.1 == *other_bytes.1)
1152 },
1153 (EncodedBytes::Utf8(bytes), EncodedBytes::Utf8(other_bytes)) => {
1154 Some(*bytes.1 == *other_bytes.1)
1155 },
1156 _ => None,
1157 };
1158
1159 if let Some(eq_result) = result {
1160 return eq_result;
1161 }
1162
1163 *self.str() == *other.str()
1164 }
1165}
1166
1167impl std::cmp::PartialEq for DOMString {
1168 fn eq(&self, other: &Self) -> bool {
1169 *self.0 == *other.0
1170 }
1171}
1172
1173impl std::cmp::Eq for TracedDOMString {}
1174
1175impl std::cmp::Eq for DOMString {}
1176
1177impl From<std::string::String> for DOMString {
1178 fn from(string: String) -> Self {
1179 TracedDOMString(RefCell::new(DOMStringType::Rust(string))).root()
1180 }
1181}
1182
1183impl From<&str> for DOMString {
1185 fn from(string: &str) -> Self {
1186 String::from(string).into()
1187 }
1188}
1189
1190impl From<DOMString> for LocalName {
1191 fn from(dom_string: DOMString) -> LocalName {
1192 dom_string.with_str_reference(|string| LocalName::from(string))
1193 }
1194}
1195
1196impl From<&DOMString> for LocalName {
1197 fn from(dom_string: &DOMString) -> LocalName {
1198 dom_string.with_str_reference(|string| LocalName::from(string))
1199 }
1200}
1201
1202impl From<DOMString> for Namespace {
1203 fn from(dom_string: DOMString) -> Namespace {
1204 dom_string.with_str_reference(|string| Namespace::from(string))
1205 }
1206}
1207
1208impl From<DOMString> for Atom {
1209 fn from(dom_string: DOMString) -> Atom {
1210 dom_string.with_str_reference(|string| Atom::from(string))
1211 }
1212}
1213
1214impl From<LocalName> for TracedDOMString {
1215 fn from(value: LocalName) -> Self {
1216 TracedDOMString(RefCell::new(DOMStringType::Atom(value.into())))
1217 }
1218}
1219
1220impl From<stylo_atoms::Atom> for TracedDOMString {
1221 fn from(value: stylo_atoms::Atom) -> Self {
1222 TracedDOMString(RefCell::new(DOMStringType::Atom(value.into())))
1223 }
1224}
1225
1226impl From<web_atoms::Namespace> for TracedDOMString {
1227 fn from(value: web_atoms::Namespace) -> Self {
1228 TracedDOMString(RefCell::new(DOMStringType::Atom(value.into())))
1229 }
1230}
1231
1232impl From<web_atoms::Prefix> for TracedDOMString {
1233 fn from(value: web_atoms::Prefix) -> Self {
1234 TracedDOMString(RefCell::new(DOMStringType::Atom(value.into())))
1235 }
1236}
1237
1238#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1239impl From<TracedDOMString> for String {
1240 fn from(val: TracedDOMString) -> Self {
1241 val.ensure_rust_string();
1242 let inner = val.0.take();
1243 match inner {
1244 DOMStringType::Rust(s) => s,
1245 DOMStringType::JSString(_) => unreachable!(),
1246 #[cfg(test)]
1247 DOMStringType::Latin1Vec(items) => String::from_utf8(items).expect("Not valid latin1"),
1248 DOMStringType::RustStatic(s) => s.to_owned(),
1249 DOMStringType::Atom(s) => s.as_ref().to_owned(),
1250 }
1251 }
1252}
1253
1254impl From<DOMString> for String {
1255 fn from(value: DOMString) -> Self {
1256 String::from(value.traced())
1257 }
1258}
1259
1260#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1261impl From<TracedDOMString> for Vec<u8> {
1262 fn from(value: TracedDOMString) -> Self {
1263 value.ensure_rust_string();
1264 let inner = value.0.take();
1265 match inner {
1266 DOMStringType::Rust(s) => s.into_bytes(),
1267 DOMStringType::JSString(_) => unreachable!(),
1268 #[cfg(test)]
1269 DOMStringType::Latin1Vec(items) => items,
1270 DOMStringType::RustStatic(_) => unreachable!(),
1271 DOMStringType::Atom(_) => unreachable!(),
1272 }
1273 }
1274}
1275
1276impl From<DOMString> for Vec<u8> {
1277 fn from(value: DOMString) -> Self {
1278 Vec::<u8>::from(value.traced())
1279 }
1280}
1281
1282impl From<Cow<'_, str>> for DOMString {
1283 fn from(value: Cow<'_, str>) -> Self {
1284 TracedDOMString(RefCell::new(DOMStringType::Rust(value.into_owned()))).root()
1285 }
1286}
1287
1288impl Zeroize for TracedDOMString {
1289 fn zeroize(&mut self) {
1290 self.0.get_mut().zeroize();
1291 }
1292}
1293
1294impl Zeroize for DOMString {
1295 fn zeroize(&mut self) {
1296 self.0.zeroize()
1297 }
1298}
1299
1300#[macro_export]
1301macro_rules! match_domstring_ascii_inner {
1302 ($variant: expr, $input: expr, $ascii_literal: literal => $then: expr, $($rest:tt)*) => {
1303 if {
1304 debug_assert!(($ascii_literal).is_ascii());
1305 $ascii_literal.as_bytes()
1306 } == $input.bytes() {
1307 $then
1308 } else {
1309 $crate::match_domstring_ascii_inner!($variant, $input, $($rest)*)
1310 }
1311
1312 };
1313 ($variant: expr, $input: expr, $p: pat => $then: expr,) => {
1314 match $input {
1315 $p => $then
1316 }
1317 }
1318}
1319
1320pub fn is_valid_floating_point_number_string(s: &str) -> bool {
1323 static RE: LazyLock<Regex> =
1324 LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
1325
1326 RE.is_match(s)
1327}
1328
1329#[macro_export]
1346macro_rules! match_domstring_ascii {
1347 ($input:expr, $($tail:tt)*) => {
1348 {
1349 use $crate::domstring::EncodedBytes;
1350 use js::context::NoGC;
1351
1352 let no_gc = unsafe { NoGC::new() };
1354 let encoded_bytes = $input.encoded_bytes(&no_gc);
1355 match encoded_bytes {
1356 EncodedBytes::Latin1(_) => {
1357 $crate::match_domstring_ascii_inner!(EncodedBytes::Latin1, encoded_bytes, $($tail)*)
1358 }
1359 EncodedBytes::Utf8(_) => {
1360 $crate::match_domstring_ascii_inner!(EncodedBytes::Utf8, encoded_bytes, $($tail)*)
1361 }
1362
1363 }
1364 }
1365 };
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::*;
1371
1372 const LATIN1_PILLCROW: u8 = 0xB6;
1373 const UTF8_PILLCROW: [u8; 2] = [194, 182];
1374 const LATIN1_POWER2: u8 = 0xB2;
1375
1376 fn from_latin1(l1vec: Vec<u8>) -> DOMString {
1377 TracedDOMString(RefCell::new(DOMStringType::Latin1Vec(l1vec))).root()
1378 }
1379
1380 #[test]
1381 fn string_functions() {
1382 let s = DOMString::from("AbBcC❤&%$#");
1383 let s_copy = s.clone();
1384 assert_eq!(s.to_ascii_lowercase(), "abbcc❤&%$#");
1385 assert_eq!(s, s_copy);
1386 assert_eq!(s.len_utf8().0, 12);
1387 assert_eq!(s_copy.len_utf8().0, 12);
1388 assert!(s.starts_with('A'));
1389 let s2 = DOMString::from("");
1390 assert!(s2.is_empty());
1391 }
1392
1393 #[test]
1394 fn string_functions_latin1() {
1395 {
1396 let s = from_latin1(vec![
1397 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1398 ]);
1399 assert_eq!(s.to_ascii_lowercase(), "abbcc&%$#²");
1400 }
1401 {
1402 let s = from_latin1(vec![b'A', b'b', b'B', b'c', b'C']);
1403 assert_eq!(s.to_ascii_lowercase(), "abbcc");
1404 }
1405 {
1406 let s = from_latin1(vec![
1407 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1408 ]);
1409 assert_eq!(s.len_utf8().0, 11);
1410 assert!(s.starts_with('A'));
1411 }
1412 {
1413 let s = from_latin1(vec![]);
1414 assert!(s.is_empty());
1415 }
1416 }
1417
1418 #[test]
1419 fn test_length() {
1420 let s1 = from_latin1(vec![
1421 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,
1422 0xAE, 0xAF,
1423 ]);
1424 let s2 = from_latin1(vec![
1425 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
1426 0xBE, 0xBF,
1427 ]);
1428 let s3 = from_latin1(vec![
1429 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
1430 0xCE, 0xCF,
1431 ]);
1432 let s4 = from_latin1(vec![
1433 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
1434 0xDE, 0xDF,
1435 ]);
1436 let s5 = from_latin1(vec![
1437 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
1438 0xEE, 0xEF,
1439 ]);
1440 let s6 = from_latin1(vec![
1441 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
1442 0xFE, 0xFF,
1443 ]);
1444
1445 let s1_utf8 = String::from("\u{00A0}¡¢£¤¥¦§¨©ª«¬\u{00AD}®¯");
1446 let s2_utf8 = String::from("°±²³´µ¶·¸¹º»¼½¾¿");
1447 let s3_utf8 = String::from("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
1448 let s4_utf8 = String::from("ÐÑÒÓÔÕÖרÙÚÛÜÝÞß");
1449 let s5_utf8 = String::from("àáâãäåæçèéêëìíîï");
1450 let s6_utf8 = String::from("ðñòóôõö÷øùúûüýþÿ");
1451
1452 assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1453 assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1454 assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1455 assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1456 assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1457 assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1458
1459 s1.ensure_rust_string();
1460 s2.ensure_rust_string();
1461 s3.ensure_rust_string();
1462 s4.ensure_rust_string();
1463 s5.ensure_rust_string();
1464 s6.ensure_rust_string();
1465 assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1466 assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1467 assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1468 assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1469 assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1470 assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1471 }
1472
1473 #[test]
1474 fn test_convert() {
1475 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1476 s.ensure_rust_string();
1477 assert_eq!(&*s.str(), "abc%$");
1478 }
1479
1480 #[test]
1481 fn partial_eq() {
1482 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1483 let string = String::from("abc%$");
1484 let s2 = DOMString::from(string.clone());
1485 let s3 = DOMString::from_static("abc%$");
1486 assert_eq!(s, s2);
1487 assert_eq!(s, string);
1488 assert_eq!(s, s3);
1489 }
1490
1491 #[test]
1492 fn encoded_latin1_bytes() {
1493 let original_latin1_bytes = vec![b'a', b'b', b'c', b'%', b'$', 0xB2];
1494 let dom_string = from_latin1(original_latin1_bytes.clone());
1495 let no_gc = unsafe { NoGC::new() };
1496 let string_latin1_bytes = match dom_string.encoded_bytes(&no_gc) {
1497 EncodedBytes::Latin1(bytes) => bytes,
1498 _ => unreachable!("Expected Latin1 encoded bytes"),
1499 };
1500 assert_eq!(*original_latin1_bytes, *string_latin1_bytes.1);
1501 }
1502
1503 #[test]
1504 fn testing_stringview() {
1505 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1506
1507 assert_eq!(
1508 s.str().chars().collect::<Vec<char>>(),
1509 vec!['a', 'b', 'c', '%', '$', '²']
1510 );
1511 assert_eq!(s.str().as_bytes(), String::from("abc%$²").as_bytes());
1512 }
1513
1514 #[test]
1519 fn test_hash() {
1520 use std::hash::{DefaultHasher, Hash, Hasher};
1521 fn hash_value(d: &DOMString) -> u64 {
1522 let mut hasher = DefaultHasher::new();
1523 d.hash(&mut hasher);
1524 hasher.finish()
1525 }
1526
1527 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1528 let s_converted = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1529 s_converted.ensure_rust_string();
1530 let s2 = DOMString::from("abc%$²");
1531 let s3 = DOMString::from_static("abc%$²");
1532
1533 let hash_s = hash_value(&s);
1534 let hash_s_converted = hash_value(&s_converted);
1535 let hash_s2 = hash_value(&s2);
1536 let hash_s3 = hash_value(&s3);
1537
1538 assert_eq!(hash_s, hash_s2);
1539 assert_eq!(hash_s, hash_s_converted);
1540 assert_eq!(hash_s, hash_s3);
1541 }
1542
1543 #[test]
1545 fn test_match_executing() {
1546 {
1548 let s = from_latin1(vec![b'a', b'b', b'c']);
1549 match_domstring_ascii!( s,
1550 "abc" => assert!(true),
1551 "bcd" => assert!(false),
1552 _ => (),
1553 );
1554 }
1555
1556 {
1557 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1558 match_domstring_ascii!( s,
1559 "abc/" => assert!(true),
1560 "bcd" => assert!(false),
1561 _ => (),
1562 );
1563 }
1564
1565 {
1566 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1567 match_domstring_ascii!( s,
1568 "bcd" => assert!(false),
1569 "abc%$" => assert!(true),
1570 _ => (),
1571 );
1572 }
1573
1574 {
1575 let s = DOMString::from("abcde");
1576 match_domstring_ascii!( s,
1577 "abc" => assert!(false),
1578 "bcd" => assert!(false),
1579 _ => assert!(true),
1580 );
1581 }
1582 {
1583 let s = DOMString::from("abc%$");
1584 match_domstring_ascii!( s,
1585 "bcd" => assert!(false),
1586 "abc%$" => assert!(true),
1587 _ => (),
1588 );
1589 }
1590 {
1591 let s = from_latin1(vec![b'a', b'b', b'c']);
1592 match_domstring_ascii!( s,
1593 "abcdd" => assert!(false),
1594 "bcd" => assert!(false),
1595 _ => (),
1596 );
1597 }
1598 {
1599 let s = DOMString::from_static("abc");
1600 match_domstring_ascii!( s,
1601 "abc" => assert!(true),
1602 "bcd" => assert!(false),
1603 _ => (),
1604 );
1605 }
1606 }
1607
1608 #[test]
1610 fn test_match_returning_result() {
1611 {
1612 let s = from_latin1(vec![b'a', b'b', b'c']);
1613 let res = match_domstring_ascii!( s,
1614 "abc" => true,
1615 "bcd" => false,
1616 _ => false,
1617 );
1618 assert_eq!(res, true);
1619 }
1620 {
1621 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1622 let res = match_domstring_ascii!( s,
1623 "abc/" => true,
1624 "bcd" => false,
1625 _ => false,
1626 );
1627 assert_eq!(res, true);
1628 }
1629 {
1630 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1631 let res = match_domstring_ascii!( s,
1632 "bcd" => false,
1633 "abc%$" => true,
1634 _ => false,
1635 );
1636 assert_eq!(res, true);
1637 }
1638
1639 {
1640 let s = DOMString::from("abcde");
1641 let res = match_domstring_ascii!( s,
1642 "abc" => false,
1643 "bcd" => false,
1644 _ => true,
1645 );
1646 assert_eq!(res, true);
1647 }
1648 {
1649 let s = DOMString::from("abc%$");
1650 let res = match_domstring_ascii!( s,
1651 "bcd" => false,
1652 "abc%$" => true,
1653 _ => false,
1654 );
1655 assert_eq!(res, true);
1656 }
1657 {
1658 let s = from_latin1(vec![b'a', b'b', b'c']);
1659 let res = match_domstring_ascii!( s,
1660 "abcdd" => false,
1661 "bcd" => false,
1662 _ => true,
1663 );
1664 assert_eq!(res, true);
1665 }
1666 }
1667
1668 #[test]
1669 #[cfg(debug_assertions)]
1670 #[should_panic]
1671 fn test_match_panic() {
1672 let s = DOMString::from("abcd");
1673 let _res = match_domstring_ascii!(s,
1674 "❤" => true,
1675 _ => false,);
1676 }
1677
1678 #[test]
1679 #[cfg(debug_assertions)]
1680 #[should_panic]
1681 fn test_match_panic2() {
1682 let s = DOMString::from("abcd");
1683 let _res = match_domstring_ascii!(s,
1684 "abc" => false,
1685 "❤" => true,
1686 _ => false,
1687 );
1688 }
1689
1690 #[test]
1691 fn test_strip_whitespace() {
1692 {
1693 let mut s = from_latin1(vec![
1694 b' ', b' ', b' ', b'\n', b' ', b'a', b'b', b'c', b'%', b'$', 0xB2, b' ',
1695 ]);
1696
1697 s.strip_leading_and_trailing_ascii_whitespace();
1698 s.ensure_rust_string();
1699 assert_eq!(&*s.str(), "abc%$²");
1700 }
1701 {
1702 let mut s = DOMString::from(" \n abc%$ ");
1703
1704 s.strip_leading_and_trailing_ascii_whitespace();
1705 s.ensure_rust_string();
1706 assert_eq!(&*s.str(), "abc%$");
1707 }
1708 {
1709 let mut s = DOMString::from_static(" \n abc%$ ");
1710
1711 s.strip_leading_and_trailing_ascii_whitespace();
1712 s.ensure_rust_string();
1713 assert_eq!(&*s.str(), "abc%$");
1714 }
1715 }
1716
1717 #[test]
1719 fn contains_html_space_characters() {
1720 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_TAB, b'a', b'a']); assert!(s.contains_html_space_characters());
1722 s.ensure_rust_string();
1723 assert!(s.contains_html_space_characters());
1724
1725 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_NEWLINE, b'a', b'a']); assert!(s.contains_html_space_characters());
1727 s.ensure_rust_string();
1728 assert!(s.contains_html_space_characters());
1729
1730 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_FORMFEED, b'a', b'a']); assert!(s.contains_html_space_characters());
1732 s.ensure_rust_string();
1733 assert!(s.contains_html_space_characters());
1734
1735 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_CR, b'a', b'a']); assert!(s.contains_html_space_characters());
1737 s.ensure_rust_string();
1738 assert!(s.contains_html_space_characters());
1739
1740 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']); assert!(s.contains_html_space_characters());
1742 s.ensure_rust_string();
1743 assert!(s.contains_html_space_characters());
1744
1745 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'a']);
1746 assert!(!s.contains_html_space_characters());
1747 s.ensure_rust_string();
1748 assert!(!s.contains_html_space_characters());
1749
1750 let s = DOMString::from_static("aba aaa");
1751 assert!(s.contains_html_space_characters());
1752 s.ensure_rust_string();
1753 assert!(s.contains_html_space_characters());
1754 }
1755
1756 #[test]
1757 fn atom() {
1758 let s = from_latin1(vec![b'a', b'a', b'a', 0x20, b'a', b'a']);
1759 let atom1 = Atom::from(s);
1760 let s2 = DOMString::from("aaa aa");
1761 let atom2 = Atom::from(s2);
1762 assert_eq!(atom1, atom2);
1763 let s3 = from_latin1(vec![b'a', b'a', b'a', 0xB2, b'a', b'a']);
1764 let atom3 = Atom::from(s3);
1765 assert_ne!(atom1, atom3);
1766 let s3 = DOMString::from_static("aaa\u{03B1}aa");
1767 let atom3 = Atom::from(s3);
1768 assert_ne!(atom1, atom3);
1769 let s4 = DOMString::from_static("aaa aa");
1770 let atom4 = Atom::from(s4);
1771 assert_eq!(atom2, atom4);
1772 }
1773
1774 #[test]
1775 fn namespace() {
1776 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1777 let atom1 = Namespace::from(s);
1778 let s2 = DOMString::from("aaa aa");
1779 let atom2 = Namespace::from(s2);
1780 assert_eq!(atom1, atom2);
1781 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1782 let atom3 = Namespace::from(s3);
1783 assert_ne!(atom1, atom3);
1784 let s4 = DOMString::from_static("aaa aa");
1785 let atom4 = Namespace::from(s4);
1786 assert_eq!(atom2, atom4);
1787 }
1788
1789 #[test]
1790 fn localname() {
1791 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1792 let atom1 = LocalName::from(s);
1793 let s2 = DOMString::from("aaa aa");
1794 let atom2 = LocalName::from(s2);
1795 assert_eq!(atom1, atom2);
1796 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1797 let atom3 = LocalName::from(s3);
1798 assert_ne!(atom1, atom3);
1799 let s4 = DOMString::from_static("aaa aa");
1800 let atom4 = LocalName::from(s4);
1801 assert_eq!(atom2, atom4);
1802 }
1803
1804 #[test]
1805 fn is_ascii_lowercase() {
1806 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1807 assert!(!s.is_ascii_lowercase());
1808 let s = from_latin1(vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a']);
1809 assert!(!s.is_ascii_lowercase());
1810 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'z']);
1811 assert!(s.is_ascii_lowercase());
1812 let s = from_latin1(vec![b'`', b'a', b'a', b'a', b'z']);
1813 assert!(!s.is_ascii_lowercase());
1814 let s = DOMString::from("`aaaz");
1815 assert!(!s.is_ascii_lowercase());
1816 let s = DOMString::from("aaaz");
1817 assert!(s.is_ascii_lowercase());
1818 let s = DOMString::from_static("aaaz");
1819 assert!(s.is_ascii_lowercase());
1820 }
1821
1822 #[test]
1823 fn test_as_bytes() {
1824 const ASCII_SMALL_A: u8 = b'a';
1825 const ASCII_SMALL_Z: u8 = b'z';
1826
1827 let v1 = vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a'];
1828 let s = from_latin1(v1.clone());
1829 {
1830 let no_gc = unsafe { NoGC::new() };
1831 assert_eq!(
1832 *s.as_bytes(&no_gc),
1833 [
1834 ASCII_SMALL_A,
1835 ASCII_SMALL_A,
1836 ASCII_SMALL_A,
1837 UTF8_PILLCROW[0],
1838 UTF8_PILLCROW[1],
1839 ASCII_SMALL_A,
1840 ASCII_SMALL_A
1841 ]
1842 );
1843 }
1844 let v2 = vec![b'a', b'a', b'a', b'a', b'z'];
1845 let s = from_latin1(v2.clone());
1846 {
1847 let no_gc = unsafe { NoGC::new() };
1848 assert_eq!(
1849 *s.as_bytes(&no_gc),
1850 [
1851 ASCII_SMALL_A,
1852 ASCII_SMALL_A,
1853 ASCII_SMALL_A,
1854 ASCII_SMALL_A,
1855 ASCII_SMALL_Z
1856 ]
1857 );
1858 }
1859
1860 let str = "abc%$²".to_owned();
1861 let s = DOMString::from(str.clone());
1862 {
1863 let no_gc = unsafe { NoGC::new() };
1864 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1865 }
1866 let str = "AbBcC❤&%$#".to_owned();
1867 let s = DOMString::from(str.clone());
1868 {
1869 let no_gc = unsafe { NoGC::new() };
1870 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1871 }
1872 let s = DOMString::from_static("AbBcC❤&%$#");
1873
1874 {
1875 let no_gc = unsafe { NoGC::new() };
1876 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1877 }
1878 }
1879
1880 #[test]
1881 fn test_atom_restore() {
1882 let local_name = LocalName::from("test_name");
1883 let local_name_domstring = TracedDOMString::from(local_name.clone());
1884 assert_eq!(local_name_domstring.str(), local_name.as_str());
1885
1886 let stylo = stylo_atoms::Atom::from("test-atom-stylo");
1887 let stylo_domstring = TracedDOMString::from(stylo.clone());
1888 assert_eq!(stylo_domstring.str(), stylo.as_str());
1889
1890 let namespace = web_atoms::Namespace::from("test-atom-namespace");
1891 let namespace_domstring = TracedDOMString::from(namespace.clone());
1892 assert_eq!(namespace_domstring.str(), namespace.as_str());
1893
1894 let prefix = web_atoms::Prefix::from("prefix");
1895 let prefix_domstring = TracedDOMString::from(prefix.clone());
1896 assert_eq!(prefix_domstring.str(), prefix.as_str());
1897 }
1898}