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(Zeroize)]
93#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
94enum DOMStringType {
95 Rust(String),
97 #[zeroize(skip)]
99 JSString(Box<Heap<*mut JSString>>),
100 #[cfg(test)]
101 Latin1Vec(Vec<u8>),
104 #[zeroize(skip)] RustStatic(&'static str),
106}
107
108impl Clone for DOMStringType {
109 fn clone(&self) -> Self {
110 match self {
111 Self::Rust(arg0) => Self::Rust(arg0.clone()),
112 Self::JSString(rooted_box) => Self::JSString(Heap::boxed(rooted_box.get())),
113 #[cfg(test)]
114 Self::Latin1Vec(arg0) => Self::Latin1Vec(arg0.clone()),
115 Self::RustStatic(arg0) => Self::RustStatic(arg0),
116 }
117 }
118}
119
120impl Default for DOMStringType {
121 fn default() -> Self {
122 Self::Rust(Default::default())
123 }
124}
125
126impl DOMStringType {
127 unsafe fn as_raw_bytes(&self) -> &[u8] {
137 match self {
138 DOMStringType::Rust(s) => s.as_bytes(),
139 DOMStringType::JSString(rooted_traceable_box) => unsafe {
140 get_latin1_string_bytes(rooted_traceable_box)
141 },
142 #[cfg(test)]
143 DOMStringType::Latin1Vec(items) => items,
144 DOMStringType::RustStatic(s) => s.as_bytes(),
145 }
146 }
147
148 fn ensure_rust_string(&mut self) -> &mut String {
149 let new_string = match self {
150 DOMStringType::Rust(string) => return string,
151 DOMStringType::JSString(rooted_traceable_box) => {
152 let cx = unsafe { JSContext::get_from_thread() };
153 let cx = cx.as_ref().expect("JS runtime has shut down");
154 unsafe { jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap()) }
155 },
156 #[cfg(test)]
157 DOMStringType::Latin1Vec(items) => {
158 let mut v = vec![0; items.len() * 2];
159 let real_size =
160 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
161 v.truncate(real_size);
162
163 unsafe { String::from_utf8_unchecked(v) }
166 },
167 DOMStringType::RustStatic(s) => s.to_owned(),
169 };
170 *self = DOMStringType::Rust(new_string);
171 self.ensure_rust_string()
172 }
173}
174
175#[derive(Debug)]
178pub struct StringView<'a>(Ref<'a, str>);
179
180impl StringView<'_> {
181 pub fn split_html_space_characters(&self) -> impl Iterator<Item = &str> {
182 self.split(HTML_SPACE_CHARACTERS)
183 .filter(|string| !string.is_empty())
184 }
185}
186
187impl From<StringView<'_>> for String {
188 fn from(string_view: StringView<'_>) -> Self {
189 string_view.0.to_string()
190 }
191}
192
193impl Deref for StringView<'_> {
194 type Target = str;
195 fn deref(&self) -> &str {
196 &(self.0)
197 }
198}
199
200impl AsRef<str> for StringView<'_> {
201 fn as_ref(&self) -> &str {
202 &(self.0)
203 }
204}
205
206impl PartialEq for StringView<'_> {
207 fn eq(&self, other: &Self) -> bool {
208 self.0.eq(&*(other.0))
209 }
210}
211
212impl PartialEq<&str> for StringView<'_> {
213 fn eq(&self, other: &&str) -> bool {
214 self.0.eq(*other)
215 }
216}
217
218impl Eq for StringView<'_> {}
219
220impl PartialOrd for StringView<'_> {
221 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
222 self.0.partial_cmp(&**other)
223 }
224}
225
226impl Ord for StringView<'_> {
227 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
228 self.0.cmp(other)
229 }
230}
231
232unsafe impl Trace for DOMStringType {
238 unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
239 unsafe {
240 match self {
241 DOMStringType::Rust(_s) => {},
242 DOMStringType::JSString(rooted_traceable_box) => rooted_traceable_box.trace(tracer),
243 #[cfg(test)]
244 DOMStringType::Latin1Vec(_s) => {},
245 DOMStringType::RustStatic(_) => {},
246 }
247 }
248 }
249}
250
251impl malloc_size_of::MallocSizeOf for DOMStringType {
252 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
253 match self {
254 DOMStringType::Rust(s) => s.size_of(ops),
255 DOMStringType::JSString(_rooted_traceable_box) => {
256 0
258 },
259 #[cfg(test)]
260 DOMStringType::Latin1Vec(s) => s.size_of(ops),
261 DOMStringType::RustStatic(_s) => 0,
262 }
263 }
264}
265
266impl std::fmt::Debug for DOMStringType {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 match self {
269 DOMStringType::Rust(s) => f.debug_struct("DOMString").field("rust_string", s).finish(),
270 DOMStringType::JSString(_rooted_traceable_box) => f.debug_struct("DOMString").finish(),
271 #[cfg(test)]
272 DOMStringType::Latin1Vec(s) => f
273 .debug_struct("DOMString")
274 .field("latin1_string", s)
275 .finish(),
276 DOMStringType::RustStatic(s) => f
277 .debug_struct("DOMString")
278 .field("static_string", s)
279 .finish(),
280 }
281 }
282}
283
284#[repr(transparent)]
322#[derive(Clone, Debug, Default, MallocSizeOf, JSTraceable)]
323#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
324pub struct TracedDOMString(RefCell<DOMStringType>);
325
326#[derive(Default, MallocSizeOf, JSTraceable)]
328pub struct DOMString(RootedTraceableBox<TracedDOMString>);
329
330impl std::fmt::Debug for DOMString {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 f.debug_tuple("DOMString").field(&*self.0).finish()
333 }
334}
335
336impl DerefMut for DOMString {
337 fn deref_mut(&mut self) -> &mut Self::Target {
338 &mut self.0
339 }
340}
341
342impl Deref for DOMString {
343 type Target = TracedDOMString;
344
345 fn deref(&self) -> &Self::Target {
346 &self.0
347 }
348}
349
350impl Clone for DOMString {
351 fn clone(&self) -> Self {
352 self.0.clone().root()
353 }
354}
355
356impl DOMString {
357 pub fn new() -> DOMString {
359 TracedDOMString::default().root()
360 }
361
362 pub fn from_js_string(
366 cx: &mut JSContext,
367 value: HandleValue,
368 ) -> Result<DOMString, DOMStringErrorType> {
369 rooted!(&in(cx) let string_ptr = unsafe { js::rust::ToString(cx, value) });
380 unsafe { DOMString::from_raw_js_string(cx, *string_ptr) }
381 }
382
383 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
390 pub unsafe fn from_raw_js_string(
391 cx: &JSContext,
392 string_ptr: *mut JSString,
393 ) -> Result<DOMString, DOMStringErrorType> {
394 if string_ptr.is_null() {
395 debug!("ToString failed");
396 Err(DOMStringErrorType::JSConversionError)
397 } else {
398 let latin1 = unsafe { js::jsapi::JS_DeprecatedStringHasLatin1Chars(string_ptr) };
399 let inner = if latin1 {
400 let h = Heap::boxed(string_ptr);
401 DOMStringType::JSString(h)
402 } else {
403 DOMStringType::Rust(unsafe {
405 jsstr_to_string(cx, NonNull::new(string_ptr).unwrap())
406 })
407 };
408 Ok(TracedDOMString(RefCell::new(inner)).root())
409 }
410 }
411
412 pub fn from_static(s: &'static str) -> DOMString {
414 TracedDOMString(RefCell::new(DOMStringType::RustStatic(s))).root()
415 }
416
417 pub fn traced(self) -> TracedDOMString {
418 *self.0.into_box()
419 }
420
421 pub fn str(&self) -> StringView<'_> {
422 self.0.str()
423 }
424}
425
426#[derive(Debug)]
427pub enum DOMStringErrorType {
428 JSConversionError,
429}
430
431impl From<String> for TracedDOMString {
432 fn from(string: String) -> Self {
433 TracedDOMString(RefCell::new(DOMStringType::Rust(string)))
434 }
435}
436
437impl TracedDOMString {
438 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
439 pub fn root(self) -> DOMString {
440 DOMString(RootedTraceableBox::from_box(Box::new(self)))
441 }
442
443 fn ensure_rust_string(&self) -> RefMut<'_, String> {
446 let inner = self.0.borrow_mut();
447 RefMut::map(inner, |inner| inner.ensure_rust_string())
448 }
449
450 #[expect(unused)]
452 fn debug_js(&self, cx: &JSContext) {
453 match *self.0.borrow() {
454 DOMStringType::Rust(ref s) => info!("Rust String ({})", s),
455 DOMStringType::JSString(ref rooted_traceable_box) => {
456 let s = unsafe {
457 jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap())
458 };
459 info!("JSString ({})", s);
460 },
461 #[cfg(test)]
462 DOMStringType::Latin1Vec(ref items) => info!("Latin1 string"),
463 DOMStringType::RustStatic(s) => info!("Static Rust String ({})", s),
464 }
465 }
466
467 pub fn str(&self) -> StringView<'_> {
469 {
470 let inner = self.0.borrow();
471 if matches!(&*inner, DOMStringType::Rust(..)) {
472 return StringView(Ref::map(inner, |inner| match inner {
473 DOMStringType::Rust(string) => string.as_str(),
474 _ => unreachable!("Guaranteed by condition above"),
475 }));
476 }
477 }
478
479 self.ensure_rust_string();
480 self.str()
481 }
482
483 pub fn encoded_bytes<'a>(&'a self, no_gc: &'a NoGC) -> EncodedBytes<'a> {
486 let inner = self.0.borrow();
487 match &*inner {
489 DOMStringType::Rust(..) | DOMStringType::RustStatic(..) => EncodedBytes::Utf8((
490 no_gc,
491 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
492 )),
493 DOMStringType::JSString(..) => EncodedBytes::Latin1((
494 no_gc,
495 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
496 )),
497 #[cfg(test)]
498 DOMStringType::Latin1Vec(..) => EncodedBytes::Latin1((
499 no_gc,
500 Ref::map(inner, |inner| unsafe { inner.as_raw_bytes() }),
501 )),
502 }
503 }
504
505 pub fn clear(&mut self) {
506 let mut inner = self.0.borrow_mut();
507 let DOMStringType::Rust(string) = &mut *inner else {
508 *inner = DOMStringType::Rust(String::new());
509 return;
510 };
511 string.clear();
512 }
513
514 pub fn is_empty(&self) -> bool {
515 let no_gc = unsafe { NoGC::new() };
517 self.encoded_bytes(&no_gc).is_empty()
518 }
519
520 pub fn len_utf8(&self) -> Utf8CodeUnits {
527 let no_gc = unsafe { NoGC::new() };
530 Utf8CodeUnits(match self.encoded_bytes(&no_gc) {
531 EncodedBytes::Utf8(bytes) => bytes.1.len() as u32,
532 EncodedBytes::Latin1(bytes) => bytes
533 .1
534 .iter()
535 .map(|&byte| if byte < 128 { 1 } else { 2 })
538 .sum::<u32>(),
539 })
540 }
541
542 pub fn len_utf8_or_latin1(&self) -> usize {
544 let no_gc = unsafe { NoGC::new() };
546 match self.encoded_bytes(&no_gc) {
547 EncodedBytes::Utf8(bytes) => bytes.1.len(),
548 EncodedBytes::Latin1(bytes) => bytes.1.len(),
549 }
550 }
551
552 pub fn len_utf16(&self) -> Utf16CodeUnits {
557 let no_gc = unsafe { NoGC::new() };
559 match self.encoded_bytes(&no_gc) {
560 EncodedBytes::Latin1(bytes) => Utf16CodeUnits(bytes.1.len() as u32),
562 EncodedBytes::Utf8(bytes) => Utf16CodeUnits::length_of(AssumeUnder4GB, unsafe {
565 str::from_utf8_unchecked(&bytes.1)
566 }),
567 }
568 }
569
570 pub fn make_ascii_lowercase(&mut self) {
573 self.0
574 .borrow_mut()
575 .ensure_rust_string()
576 .make_ascii_lowercase();
577 }
578
579 pub fn push_str(&mut self, string_to_push: &str) {
580 self.0
581 .borrow_mut()
582 .ensure_rust_string()
583 .push_str(string_to_push);
584 }
585
586 pub fn strip_leading_and_trailing_ascii_whitespace(&mut self) {
588 if self.is_empty() {
589 return;
590 }
591
592 let mut inner = self.0.borrow_mut();
593 let string = inner.ensure_rust_string();
594 let trailing_whitespace_len = string
595 .trim_end_matches(|character: char| character.is_ascii_whitespace())
596 .len();
597 string.truncate(trailing_whitespace_len);
598 if string.is_empty() {
599 return;
600 }
601
602 let first_non_whitespace = string
603 .find(|character: char| !character.is_ascii_whitespace())
604 .unwrap();
605 string.replace_range(0..first_non_whitespace, "");
606 }
607
608 pub fn is_valid_floating_point_number_string(&self) -> bool {
610 is_valid_floating_point_number_string(self.0.borrow_mut().ensure_rust_string()) &&
611 self.parse_floating_point_number().is_some()
612 }
613
614 pub fn parse<T: FromStr>(&self) -> Result<T, <T as FromStr>::Err> {
615 self.str().parse::<T>()
616 }
617
618 pub fn parse_floating_point_number(&self) -> Option<f64> {
620 parse_floating_point_number(&self.str())
621 }
622
623 pub fn set_best_representation_of_the_floating_point_number(&mut self) {
625 if let Some(val) = self.parse_floating_point_number() {
626 let parsed_value = if val.is_zero() { 0.0_f64 } else { val };
628
629 *self.0.borrow_mut() = DOMStringType::Rust(parsed_value.to_string());
630 }
631 }
632
633 pub fn to_lowercase(&self) -> String {
634 self.str().to_lowercase()
635 }
636
637 pub fn to_uppercase(&self) -> String {
638 self.str().to_uppercase()
639 }
640
641 pub fn strip_newlines(&mut self) {
642 self.0
645 .borrow_mut()
646 .ensure_rust_string()
647 .retain(|character| character != '\r' && character != '\n');
648 }
649
650 pub fn normalize_newlines(&mut self) {
652 let mut inner = self.0.borrow_mut();
656 let string = inner.ensure_rust_string();
657 *string = string.replace("\r\n", "\n").replace("\r", "\n")
658 }
659
660 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
661 pub fn replace(self, needle: &str, replace_char: &str) -> DOMString {
662 let new_string = self.str().to_owned();
663 TracedDOMString(RefCell::new(DOMStringType::Rust(
664 new_string.replace(needle, replace_char),
665 )))
666 .root()
667 }
668
669 pub fn starts_with(&self, c: char) -> bool {
671 if !c.is_ascii() {
672 self.str().starts_with(c)
673 } else {
674 let no_gc = unsafe { NoGC::new() };
678 self.encoded_bytes(&no_gc).bytes().starts_with(&[c as u8])
679 }
680 }
681
682 pub fn starts_with_str(&self, needle: &str) -> bool {
683 self.str().starts_with(needle)
684 }
685
686 pub fn ends_with_str(&self, needle: &str) -> bool {
687 self.str().ends_with(needle)
688 }
689
690 pub fn contains(&self, needle: &str) -> bool {
691 self.str().contains(needle)
692 }
693
694 pub fn eq_ignore_ascii_case(&self, other: &str) -> bool {
699 if other.is_ascii() {
700 let no_gc = unsafe { NoGC::new() };
702 self.encoded_bytes(&no_gc)
703 .bytes()
704 .eq_ignore_ascii_case(other.as_bytes())
705 } else {
706 self.str().eq_ignore_ascii_case(other)
707 }
708 }
709
710 pub fn to_ascii_lowercase(&self) -> String {
711 let no_gc = unsafe { NoGC::new() };
713 let conversion = match self.encoded_bytes(&no_gc) {
714 EncodedBytes::Latin1(bytes) => {
715 if bytes.1.iter().all(|c| *c <= ASCII_END) {
716 Some(unsafe {
718 String::from_utf8_unchecked(
719 bytes
720 .1
721 .iter()
722 .map(|c| {
723 if *c >= ASCII_CAPITAL_A && *c <= ASCII_CAPITAL_Z {
724 c + 32
725 } else {
726 *c
727 }
728 })
729 .collect(),
730 )
731 })
732 } else {
733 None
734 }
735 },
736 EncodedBytes::Utf8(bytes) => unsafe {
737 Some(str::from_utf8_unchecked(&bytes.1).to_ascii_lowercase())
739 },
740 };
741 if let Some(conversion) = conversion {
743 conversion
744 } else {
745 self.str().to_ascii_lowercase()
746 }
747 }
748
749 fn contains_space_characters(
750 &self,
751 latin1_characters: &'static [u8],
752 utf8_characters: &'static [char],
753 ) -> bool {
754 let no_gc = unsafe { NoGC::new() };
756 match self.encoded_bytes(&no_gc) {
757 EncodedBytes::Latin1(items) => {
758 latin1_characters.iter().any(|byte| items.1.contains(byte))
759 },
760 EncodedBytes::Utf8(bytes) => {
761 let s = unsafe { str::from_utf8_unchecked(&bytes.1) };
763 s.contains(utf8_characters)
764 },
765 }
766 }
767
768 pub fn contains_tab_or_newline(&self) -> bool {
770 const LATIN_TAB_OR_NEWLINE: [u8; 3] = [ASCII_TAB, ASCII_NEWLINE, ASCII_CR];
771 const UTF8_TAB_OR_NEWLINE: [char; 3] = ['\u{0009}', '\u{000a}', '\u{000d}'];
772
773 self.contains_space_characters(&LATIN_TAB_OR_NEWLINE, &UTF8_TAB_OR_NEWLINE)
774 }
775
776 pub fn contains_html_space_characters(&self) -> bool {
778 const SPACE_BYTES: [u8; 5] = [
779 ASCII_TAB,
780 ASCII_NEWLINE,
781 ASCII_FORMFEED,
782 ASCII_CR,
783 ASCII_SPACE,
784 ];
785 self.contains_space_characters(&SPACE_BYTES, HTML_SPACE_CHARACTERS)
786 }
787
788 pub fn as_bytes<'a>(&'a self, no_gc: &'a NoGC) -> BytesView<'a> {
790 if self.is_ascii() {
794 BytesView {
795 _no_gc: no_gc,
796 inner: self.0.borrow(),
797 }
798 } else {
799 self.ensure_rust_string();
800 BytesView {
801 _no_gc: no_gc,
802 inner: self.0.borrow(),
803 }
804 }
805 }
806
807 pub fn is_ascii_lowercase(&self) -> bool {
809 let no_gc = unsafe { NoGC::new() };
811 match self.encoded_bytes(&no_gc) {
812 EncodedBytes::Latin1(items) => items
813 .1
814 .iter()
815 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(c)),
816 EncodedBytes::Utf8(bytes) => bytes
817 .1
818 .iter()
819 .map(|c| c.to_u8().unwrap_or(ASCII_LOWERCASE_A - 1))
820 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(&c)),
821 }
822 }
823
824 pub fn is_ascii(&self) -> bool {
826 let no_gc = unsafe { NoGC::new() };
828 self.encoded_bytes(&no_gc).bytes().is_ascii()
829 }
830
831 pub fn is_valid_for_cookie(&self) -> bool {
835 let no_gc = unsafe { NoGC::new() };
837 match self.encoded_bytes(&no_gc) {
838 EncodedBytes::Latin1(items) | EncodedBytes::Utf8(items) => !items
839 .1
840 .iter()
841 .any(|c| *c == 0x7f || (*c <= 0x1f && *c != 0x09)),
842 }
843 }
844
845 fn with_str_reference<Result>(&self, callback: fn(&str) -> Result) -> Result {
848 let no_gc = unsafe { NoGC::new() };
851 match self.encoded_bytes(&no_gc) {
852 EncodedBytes::Latin1(latin1_bytes) => {
854 if latin1_bytes.1.iter().all(|character| character.is_ascii()) {
855 return callback(unsafe { str::from_utf8_unchecked(&latin1_bytes.1) });
858 }
859 },
860 EncodedBytes::Utf8(utf8_bytes) => {
861 return callback(unsafe { str::from_utf8_unchecked(&utf8_bytes.1) });
864 },
865 };
866 callback(self.str().deref())
867 }
868
869 pub fn normalize_crlf(&self) -> String {
877 let s = self.str();
878 let mut buf = String::new();
879 let mut prev = ' ';
880 for ch in s.chars() {
881 match ch {
882 '\n' if prev != '\r' => {
883 buf.push('\r');
884 buf.push('\n');
885 },
886 '\n' => {
887 buf.push('\n');
888 },
889 _ if prev == '\r' => {
892 buf.push('\n');
893 buf.push(ch);
894 },
895 _ => buf.push(ch),
896 };
897 prev = ch;
898 }
899 if prev == '\r' {
901 buf.push('\n');
902 }
903 buf
904 }
905}
906
907pub fn parse_floating_point_number(input: &str) -> Option<f64> {
909 input.trim().parse::<f64>().ok().filter(|value| {
915 !(value.is_infinite() || value.is_nan() || input.ends_with('.') || input.starts_with('+'))
918 })
919}
920
921pub struct BytesView<'a> {
922 _no_gc: &'a NoGC,
923 inner: Ref<'a, DOMStringType>,
924}
925
926impl Deref for BytesView<'_> {
927 type Target = [u8];
928
929 fn deref(&self) -> &Self::Target {
930 unsafe { self.inner.as_raw_bytes() }
932 }
933}
934
935impl Ord for TracedDOMString {
936 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
937 self.str().cmp(&other.str())
938 }
939}
940
941impl PartialOrd for TracedDOMString {
942 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
943 self.str().partial_cmp(&other.str())
944 }
945}
946
947impl PartialOrd for DOMString {
948 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
949 self.0.partial_cmp(&other.0)
950 }
951}
952
953impl Extend<char> for TracedDOMString {
954 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
955 self.0.borrow_mut().ensure_rust_string().extend(iter)
956 }
957}
958
959impl ToJSValConvertible for DOMString {
960 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
961 let val = self.0.0.borrow();
962 match *val {
963 DOMStringType::Rust(ref s) => s.to_jsval(cx, rval),
964 DOMStringType::JSString(ref rooted_traceable_box) => unsafe {
965 rval.set(StringValue(&*rooted_traceable_box.get()));
966 },
967 #[cfg(test)]
968 DOMStringType::Latin1Vec(ref items) => {
969 let mut v = vec![0; items.len() * 2];
970 let real_size =
971 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
972 v.truncate(real_size);
973
974 String::from_utf8(v)
975 .expect("Error in constructin test string")
976 .to_jsval(cx, rval);
977 },
978 DOMStringType::RustStatic(s) => s.to_jsval(cx, rval),
979 };
980 }
981}
982
983impl std::hash::Hash for TracedDOMString {
984 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
985 self.str().hash(state);
986 }
987}
988
989impl std::hash::Hash for DOMString {
990 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
991 self.str().hash(state);
992 }
993}
994
995impl std::fmt::Display for TracedDOMString {
996 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
997 fmt::Display::fmt(self.str().deref(), f)
998 }
999}
1000
1001impl std::fmt::Display for DOMString {
1002 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1003 fmt::Display::fmt(self.str().deref(), f)
1004 }
1005}
1006
1007impl std::cmp::PartialEq<str> for TracedDOMString {
1008 fn eq(&self, other: &str) -> bool {
1009 if other.is_ascii() {
1010 let no_gc = unsafe { NoGC::new() };
1012 *other.as_bytes() == *self.encoded_bytes(&no_gc).bytes()
1013 } else {
1014 self.str().deref() == other
1015 }
1016 }
1017}
1018
1019impl std::cmp::PartialEq<str> for DOMString {
1020 fn eq(&self, other: &str) -> bool {
1021 self.0.eq(other)
1022 }
1023}
1024
1025impl std::cmp::PartialEq<&str> for TracedDOMString {
1026 fn eq(&self, other: &&str) -> bool {
1027 self.eq(*other)
1028 }
1029}
1030
1031impl std::cmp::PartialEq<&str> for DOMString {
1032 fn eq(&self, other: &&str) -> bool {
1033 self.0.eq(*other)
1034 }
1035}
1036
1037impl std::cmp::PartialEq<String> for TracedDOMString {
1038 fn eq(&self, other: &String) -> bool {
1039 self.eq(other.as_str())
1040 }
1041}
1042
1043impl std::cmp::PartialEq<String> for DOMString {
1044 fn eq(&self, other: &String) -> bool {
1045 self.0.eq(other.as_str())
1046 }
1047}
1048
1049impl std::cmp::PartialEq<TracedDOMString> for String {
1050 fn eq(&self, other: &TracedDOMString) -> bool {
1051 other.eq(self)
1052 }
1053}
1054
1055impl std::cmp::PartialEq<DOMString> for String {
1056 fn eq(&self, other: &DOMString) -> bool {
1057 other.0.eq(self)
1058 }
1059}
1060
1061impl std::cmp::PartialEq<TracedDOMString> for str {
1062 fn eq(&self, other: &TracedDOMString) -> bool {
1063 other.eq(self)
1064 }
1065}
1066
1067impl std::cmp::PartialEq<DOMString> for str {
1068 fn eq(&self, other: &DOMString) -> bool {
1069 other.0.eq(self)
1070 }
1071}
1072
1073impl std::cmp::PartialEq for TracedDOMString {
1074 fn eq(&self, other: &TracedDOMString) -> bool {
1075 let no_gc = unsafe { NoGC::new() };
1076 let result = match (self.encoded_bytes(&no_gc), other.encoded_bytes(&no_gc)) {
1077 (EncodedBytes::Latin1(bytes), EncodedBytes::Latin1(other_bytes)) => {
1078 Some(*bytes.1 == *other_bytes.1)
1079 },
1080 (EncodedBytes::Latin1(bytes), EncodedBytes::Utf8(other_bytes))
1081 if other_bytes.1.is_ascii() =>
1082 {
1083 Some(*bytes.1 == *other_bytes.1)
1084 },
1085 (EncodedBytes::Utf8(bytes), EncodedBytes::Latin1(other_bytes))
1086 if bytes.1.is_ascii() =>
1087 {
1088 Some(*bytes.1 == *other_bytes.1)
1089 },
1090 (EncodedBytes::Utf8(bytes), EncodedBytes::Utf8(other_bytes)) => {
1091 Some(*bytes.1 == *other_bytes.1)
1092 },
1093 _ => None,
1094 };
1095
1096 if let Some(eq_result) = result {
1097 return eq_result;
1098 }
1099
1100 *self.str() == *other.str()
1101 }
1102}
1103
1104impl std::cmp::PartialEq for DOMString {
1105 fn eq(&self, other: &Self) -> bool {
1106 *self.0 == *other.0
1107 }
1108}
1109
1110impl std::cmp::Eq for TracedDOMString {}
1111
1112impl std::cmp::Eq for DOMString {}
1113
1114impl From<std::string::String> for DOMString {
1115 fn from(string: String) -> Self {
1116 TracedDOMString(RefCell::new(DOMStringType::Rust(string))).root()
1117 }
1118}
1119
1120impl From<&str> for DOMString {
1122 fn from(string: &str) -> Self {
1123 String::from(string).into()
1124 }
1125}
1126
1127impl From<DOMString> for LocalName {
1128 fn from(dom_string: DOMString) -> LocalName {
1129 dom_string.with_str_reference(|string| LocalName::from(string))
1130 }
1131}
1132
1133impl From<&DOMString> for LocalName {
1134 fn from(dom_string: &DOMString) -> LocalName {
1135 dom_string.with_str_reference(|string| LocalName::from(string))
1136 }
1137}
1138
1139impl From<DOMString> for Namespace {
1140 fn from(dom_string: DOMString) -> Namespace {
1141 dom_string.with_str_reference(|string| Namespace::from(string))
1142 }
1143}
1144
1145impl From<DOMString> for Atom {
1146 fn from(dom_string: DOMString) -> Atom {
1147 dom_string.with_str_reference(|string| Atom::from(string))
1148 }
1149}
1150
1151#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1152impl From<TracedDOMString> for String {
1153 fn from(val: TracedDOMString) -> Self {
1154 val.ensure_rust_string();
1155 let inner = val.0.take();
1156 match inner {
1157 DOMStringType::Rust(s) => s,
1158 DOMStringType::JSString(_) => unreachable!(),
1159 #[cfg(test)]
1160 DOMStringType::Latin1Vec(items) => String::from_utf8(items).expect("Not valid latin1"),
1161 DOMStringType::RustStatic(s) => s.to_owned(),
1162 }
1163 }
1164}
1165
1166impl From<DOMString> for String {
1167 fn from(value: DOMString) -> Self {
1168 String::from(value.traced())
1169 }
1170}
1171
1172#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1173impl From<TracedDOMString> for Vec<u8> {
1174 fn from(value: TracedDOMString) -> Self {
1175 value.ensure_rust_string();
1176 let inner = value.0.take();
1177 match inner {
1178 DOMStringType::Rust(s) => s.into_bytes(),
1179 DOMStringType::JSString(_) => unreachable!(),
1180 #[cfg(test)]
1181 DOMStringType::Latin1Vec(items) => items,
1182 DOMStringType::RustStatic(_) => unreachable!(),
1183 }
1184 }
1185}
1186
1187impl From<DOMString> for Vec<u8> {
1188 fn from(value: DOMString) -> Self {
1189 Vec::<u8>::from(value.traced())
1190 }
1191}
1192
1193impl From<Cow<'_, str>> for DOMString {
1194 fn from(value: Cow<'_, str>) -> Self {
1195 TracedDOMString(RefCell::new(DOMStringType::Rust(value.into_owned()))).root()
1196 }
1197}
1198
1199impl Zeroize for TracedDOMString {
1200 fn zeroize(&mut self) {
1201 self.0.get_mut().zeroize();
1202 }
1203}
1204
1205impl Zeroize for DOMString {
1206 fn zeroize(&mut self) {
1207 self.0.zeroize()
1208 }
1209}
1210
1211#[macro_export]
1212macro_rules! match_domstring_ascii_inner {
1213 ($variant: expr, $input: expr, $ascii_literal: literal => $then: expr, $($rest:tt)*) => {
1214 if {
1215 debug_assert!(($ascii_literal).is_ascii());
1216 $ascii_literal.as_bytes()
1217 } == $input.bytes() {
1218 $then
1219 } else {
1220 $crate::match_domstring_ascii_inner!($variant, $input, $($rest)*)
1221 }
1222
1223 };
1224 ($variant: expr, $input: expr, $p: pat => $then: expr,) => {
1225 match $input {
1226 $p => $then
1227 }
1228 }
1229}
1230
1231pub fn is_valid_floating_point_number_string(s: &str) -> bool {
1234 static RE: LazyLock<Regex> =
1235 LazyLock::new(|| Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap());
1236
1237 RE.is_match(s)
1238}
1239
1240#[macro_export]
1257macro_rules! match_domstring_ascii {
1258 ($input:expr, $($tail:tt)*) => {
1259 {
1260 use $crate::domstring::EncodedBytes;
1261 use js::context::NoGC;
1262
1263 let no_gc = unsafe { NoGC::new() };
1265 let encoded_bytes = $input.encoded_bytes(&no_gc);
1266 match encoded_bytes {
1267 EncodedBytes::Latin1(_) => {
1268 $crate::match_domstring_ascii_inner!(EncodedBytes::Latin1, encoded_bytes, $($tail)*)
1269 }
1270 EncodedBytes::Utf8(_) => {
1271 $crate::match_domstring_ascii_inner!(EncodedBytes::Utf8, encoded_bytes, $($tail)*)
1272 }
1273
1274 }
1275 }
1276 };
1277}
1278
1279#[cfg(test)]
1280mod tests {
1281 use super::*;
1282
1283 const LATIN1_PILLCROW: u8 = 0xB6;
1284 const UTF8_PILLCROW: [u8; 2] = [194, 182];
1285 const LATIN1_POWER2: u8 = 0xB2;
1286
1287 fn from_latin1(l1vec: Vec<u8>) -> DOMString {
1288 TracedDOMString(RefCell::new(DOMStringType::Latin1Vec(l1vec))).root()
1289 }
1290
1291 #[test]
1292 fn string_functions() {
1293 let s = DOMString::from("AbBcC❤&%$#");
1294 let s_copy = s.clone();
1295 assert_eq!(s.to_ascii_lowercase(), "abbcc❤&%$#");
1296 assert_eq!(s, s_copy);
1297 assert_eq!(s.len_utf8().0, 12);
1298 assert_eq!(s_copy.len_utf8().0, 12);
1299 assert!(s.starts_with('A'));
1300 let s2 = DOMString::from("");
1301 assert!(s2.is_empty());
1302 }
1303
1304 #[test]
1305 fn string_functions_latin1() {
1306 {
1307 let s = from_latin1(vec![
1308 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1309 ]);
1310 assert_eq!(s.to_ascii_lowercase(), "abbcc&%$#²");
1311 }
1312 {
1313 let s = from_latin1(vec![b'A', b'b', b'B', b'c', b'C']);
1314 assert_eq!(s.to_ascii_lowercase(), "abbcc");
1315 }
1316 {
1317 let s = from_latin1(vec![
1318 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1319 ]);
1320 assert_eq!(s.len_utf8().0, 11);
1321 assert!(s.starts_with('A'));
1322 }
1323 {
1324 let s = from_latin1(vec![]);
1325 assert!(s.is_empty());
1326 }
1327 }
1328
1329 #[test]
1330 fn test_length() {
1331 let s1 = from_latin1(vec![
1332 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,
1333 0xAE, 0xAF,
1334 ]);
1335 let s2 = from_latin1(vec![
1336 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
1337 0xBE, 0xBF,
1338 ]);
1339 let s3 = from_latin1(vec![
1340 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
1341 0xCE, 0xCF,
1342 ]);
1343 let s4 = from_latin1(vec![
1344 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
1345 0xDE, 0xDF,
1346 ]);
1347 let s5 = from_latin1(vec![
1348 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
1349 0xEE, 0xEF,
1350 ]);
1351 let s6 = from_latin1(vec![
1352 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
1353 0xFE, 0xFF,
1354 ]);
1355
1356 let s1_utf8 = String::from("\u{00A0}¡¢£¤¥¦§¨©ª«¬\u{00AD}®¯");
1357 let s2_utf8 = String::from("°±²³´µ¶·¸¹º»¼½¾¿");
1358 let s3_utf8 = String::from("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
1359 let s4_utf8 = String::from("ÐÑÒÓÔÕÖרÙÚÛÜÝÞß");
1360 let s5_utf8 = String::from("àáâãäåæçèéêëìíîï");
1361 let s6_utf8 = String::from("ðñòóôõö÷øùúûüýþÿ");
1362
1363 assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1364 assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1365 assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1366 assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1367 assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1368 assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1369
1370 s1.ensure_rust_string();
1371 s2.ensure_rust_string();
1372 s3.ensure_rust_string();
1373 s4.ensure_rust_string();
1374 s5.ensure_rust_string();
1375 s6.ensure_rust_string();
1376 assert_eq!(usize::from(s1.len_utf8()), s1_utf8.len());
1377 assert_eq!(usize::from(s2.len_utf8()), s2_utf8.len());
1378 assert_eq!(usize::from(s3.len_utf8()), s3_utf8.len());
1379 assert_eq!(usize::from(s4.len_utf8()), s4_utf8.len());
1380 assert_eq!(usize::from(s5.len_utf8()), s5_utf8.len());
1381 assert_eq!(usize::from(s6.len_utf8()), s6_utf8.len());
1382 }
1383
1384 #[test]
1385 fn test_convert() {
1386 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1387 s.ensure_rust_string();
1388 assert_eq!(&*s.str(), "abc%$");
1389 }
1390
1391 #[test]
1392 fn partial_eq() {
1393 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1394 let string = String::from("abc%$");
1395 let s2 = DOMString::from(string.clone());
1396 let s3 = DOMString::from_static("abc%$");
1397 assert_eq!(s, s2);
1398 assert_eq!(s, string);
1399 assert_eq!(s, s3);
1400 }
1401
1402 #[test]
1403 fn encoded_latin1_bytes() {
1404 let original_latin1_bytes = vec![b'a', b'b', b'c', b'%', b'$', 0xB2];
1405 let dom_string = from_latin1(original_latin1_bytes.clone());
1406 let no_gc = unsafe { NoGC::new() };
1407 let string_latin1_bytes = match dom_string.encoded_bytes(&no_gc) {
1408 EncodedBytes::Latin1(bytes) => bytes,
1409 _ => unreachable!("Expected Latin1 encoded bytes"),
1410 };
1411 assert_eq!(*original_latin1_bytes, *string_latin1_bytes.1);
1412 }
1413
1414 #[test]
1415 fn testing_stringview() {
1416 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1417
1418 assert_eq!(
1419 s.str().chars().collect::<Vec<char>>(),
1420 vec!['a', 'b', 'c', '%', '$', '²']
1421 );
1422 assert_eq!(s.str().as_bytes(), String::from("abc%$²").as_bytes());
1423 }
1424
1425 #[test]
1430 fn test_hash() {
1431 use std::hash::{DefaultHasher, Hash, Hasher};
1432 fn hash_value(d: &DOMString) -> u64 {
1433 let mut hasher = DefaultHasher::new();
1434 d.hash(&mut hasher);
1435 hasher.finish()
1436 }
1437
1438 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1439 let s_converted = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1440 s_converted.ensure_rust_string();
1441 let s2 = DOMString::from("abc%$²");
1442 let s3 = DOMString::from_static("abc%$²");
1443
1444 let hash_s = hash_value(&s);
1445 let hash_s_converted = hash_value(&s_converted);
1446 let hash_s2 = hash_value(&s2);
1447 let hash_s3 = hash_value(&s3);
1448
1449 assert_eq!(hash_s, hash_s2);
1450 assert_eq!(hash_s, hash_s_converted);
1451 assert_eq!(hash_s, hash_s3);
1452 }
1453
1454 #[test]
1456 fn test_match_executing() {
1457 {
1459 let s = from_latin1(vec![b'a', b'b', b'c']);
1460 match_domstring_ascii!( s,
1461 "abc" => assert!(true),
1462 "bcd" => assert!(false),
1463 _ => (),
1464 );
1465 }
1466
1467 {
1468 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1469 match_domstring_ascii!( s,
1470 "abc/" => assert!(true),
1471 "bcd" => assert!(false),
1472 _ => (),
1473 );
1474 }
1475
1476 {
1477 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1478 match_domstring_ascii!( s,
1479 "bcd" => assert!(false),
1480 "abc%$" => assert!(true),
1481 _ => (),
1482 );
1483 }
1484
1485 {
1486 let s = DOMString::from("abcde");
1487 match_domstring_ascii!( s,
1488 "abc" => assert!(false),
1489 "bcd" => assert!(false),
1490 _ => assert!(true),
1491 );
1492 }
1493 {
1494 let s = DOMString::from("abc%$");
1495 match_domstring_ascii!( s,
1496 "bcd" => assert!(false),
1497 "abc%$" => assert!(true),
1498 _ => (),
1499 );
1500 }
1501 {
1502 let s = from_latin1(vec![b'a', b'b', b'c']);
1503 match_domstring_ascii!( s,
1504 "abcdd" => assert!(false),
1505 "bcd" => assert!(false),
1506 _ => (),
1507 );
1508 }
1509 {
1510 let s = DOMString::from_static("abc");
1511 match_domstring_ascii!( s,
1512 "abc" => assert!(true),
1513 "bcd" => assert!(false),
1514 _ => (),
1515 );
1516 }
1517 }
1518
1519 #[test]
1521 fn test_match_returning_result() {
1522 {
1523 let s = from_latin1(vec![b'a', b'b', b'c']);
1524 let res = match_domstring_ascii!( s,
1525 "abc" => true,
1526 "bcd" => false,
1527 _ => false,
1528 );
1529 assert_eq!(res, true);
1530 }
1531 {
1532 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1533 let res = match_domstring_ascii!( s,
1534 "abc/" => true,
1535 "bcd" => false,
1536 _ => false,
1537 );
1538 assert_eq!(res, true);
1539 }
1540 {
1541 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1542 let res = match_domstring_ascii!( s,
1543 "bcd" => false,
1544 "abc%$" => true,
1545 _ => false,
1546 );
1547 assert_eq!(res, true);
1548 }
1549
1550 {
1551 let s = DOMString::from("abcde");
1552 let res = match_domstring_ascii!( s,
1553 "abc" => false,
1554 "bcd" => false,
1555 _ => true,
1556 );
1557 assert_eq!(res, true);
1558 }
1559 {
1560 let s = DOMString::from("abc%$");
1561 let res = match_domstring_ascii!( s,
1562 "bcd" => false,
1563 "abc%$" => true,
1564 _ => false,
1565 );
1566 assert_eq!(res, true);
1567 }
1568 {
1569 let s = from_latin1(vec![b'a', b'b', b'c']);
1570 let res = match_domstring_ascii!( s,
1571 "abcdd" => false,
1572 "bcd" => false,
1573 _ => true,
1574 );
1575 assert_eq!(res, true);
1576 }
1577 }
1578
1579 #[test]
1580 #[cfg(debug_assertions)]
1581 #[should_panic]
1582 fn test_match_panic() {
1583 let s = DOMString::from("abcd");
1584 let _res = match_domstring_ascii!(s,
1585 "❤" => true,
1586 _ => false,);
1587 }
1588
1589 #[test]
1590 #[cfg(debug_assertions)]
1591 #[should_panic]
1592 fn test_match_panic2() {
1593 let s = DOMString::from("abcd");
1594 let _res = match_domstring_ascii!(s,
1595 "abc" => false,
1596 "❤" => true,
1597 _ => false,
1598 );
1599 }
1600
1601 #[test]
1602 fn test_strip_whitespace() {
1603 {
1604 let mut s = from_latin1(vec![
1605 b' ', b' ', b' ', b'\n', b' ', b'a', b'b', b'c', b'%', b'$', 0xB2, b' ',
1606 ]);
1607
1608 s.strip_leading_and_trailing_ascii_whitespace();
1609 s.ensure_rust_string();
1610 assert_eq!(&*s.str(), "abc%$²");
1611 }
1612 {
1613 let mut s = DOMString::from(" \n abc%$ ");
1614
1615 s.strip_leading_and_trailing_ascii_whitespace();
1616 s.ensure_rust_string();
1617 assert_eq!(&*s.str(), "abc%$");
1618 }
1619 {
1620 let mut s = DOMString::from_static(" \n abc%$ ");
1621
1622 s.strip_leading_and_trailing_ascii_whitespace();
1623 s.ensure_rust_string();
1624 assert_eq!(&*s.str(), "abc%$");
1625 }
1626 }
1627
1628 #[test]
1630 fn contains_html_space_characters() {
1631 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_TAB, b'a', b'a']); assert!(s.contains_html_space_characters());
1633 s.ensure_rust_string();
1634 assert!(s.contains_html_space_characters());
1635
1636 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_NEWLINE, b'a', b'a']); assert!(s.contains_html_space_characters());
1638 s.ensure_rust_string();
1639 assert!(s.contains_html_space_characters());
1640
1641 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_FORMFEED, b'a', b'a']); assert!(s.contains_html_space_characters());
1643 s.ensure_rust_string();
1644 assert!(s.contains_html_space_characters());
1645
1646 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_CR, b'a', b'a']); assert!(s.contains_html_space_characters());
1648 s.ensure_rust_string();
1649 assert!(s.contains_html_space_characters());
1650
1651 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']); assert!(s.contains_html_space_characters());
1653 s.ensure_rust_string();
1654 assert!(s.contains_html_space_characters());
1655
1656 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'a']);
1657 assert!(!s.contains_html_space_characters());
1658 s.ensure_rust_string();
1659 assert!(!s.contains_html_space_characters());
1660
1661 let s = DOMString::from_static("aba aaa");
1662 assert!(s.contains_html_space_characters());
1663 s.ensure_rust_string();
1664 assert!(s.contains_html_space_characters());
1665 }
1666
1667 #[test]
1668 fn atom() {
1669 let s = from_latin1(vec![b'a', b'a', b'a', 0x20, b'a', b'a']);
1670 let atom1 = Atom::from(s);
1671 let s2 = DOMString::from("aaa aa");
1672 let atom2 = Atom::from(s2);
1673 assert_eq!(atom1, atom2);
1674 let s3 = from_latin1(vec![b'a', b'a', b'a', 0xB2, b'a', b'a']);
1675 let atom3 = Atom::from(s3);
1676 assert_ne!(atom1, atom3);
1677 let s3 = DOMString::from_static("aaa\u{03B1}aa");
1678 let atom3 = Atom::from(s3);
1679 assert_ne!(atom1, atom3);
1680 let s4 = DOMString::from_static("aaa aa");
1681 let atom4 = Atom::from(s4);
1682 assert_eq!(atom2, atom4);
1683 }
1684
1685 #[test]
1686 fn namespace() {
1687 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1688 let atom1 = Namespace::from(s);
1689 let s2 = DOMString::from("aaa aa");
1690 let atom2 = Namespace::from(s2);
1691 assert_eq!(atom1, atom2);
1692 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1693 let atom3 = Namespace::from(s3);
1694 assert_ne!(atom1, atom3);
1695 let s4 = DOMString::from_static("aaa aa");
1696 let atom4 = Namespace::from(s4);
1697 assert_eq!(atom2, atom4);
1698 }
1699
1700 #[test]
1701 fn localname() {
1702 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1703 let atom1 = LocalName::from(s);
1704 let s2 = DOMString::from("aaa aa");
1705 let atom2 = LocalName::from(s2);
1706 assert_eq!(atom1, atom2);
1707 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1708 let atom3 = LocalName::from(s3);
1709 assert_ne!(atom1, atom3);
1710 let s4 = DOMString::from_static("aaa aa");
1711 let atom4 = LocalName::from(s4);
1712 assert_eq!(atom2, atom4);
1713 }
1714
1715 #[test]
1716 fn is_ascii_lowercase() {
1717 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1718 assert!(!s.is_ascii_lowercase());
1719 let s = from_latin1(vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a']);
1720 assert!(!s.is_ascii_lowercase());
1721 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'z']);
1722 assert!(s.is_ascii_lowercase());
1723 let s = from_latin1(vec![b'`', b'a', b'a', b'a', b'z']);
1724 assert!(!s.is_ascii_lowercase());
1725 let s = DOMString::from("`aaaz");
1726 assert!(!s.is_ascii_lowercase());
1727 let s = DOMString::from("aaaz");
1728 assert!(s.is_ascii_lowercase());
1729 let s = DOMString::from_static("aaaz");
1730 assert!(s.is_ascii_lowercase());
1731 }
1732
1733 #[test]
1734 fn test_as_bytes() {
1735 const ASCII_SMALL_A: u8 = b'a';
1736 const ASCII_SMALL_Z: u8 = b'z';
1737
1738 let v1 = vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a'];
1739 let s = from_latin1(v1.clone());
1740 {
1741 let no_gc = unsafe { NoGC::new() };
1742 assert_eq!(
1743 *s.as_bytes(&no_gc),
1744 [
1745 ASCII_SMALL_A,
1746 ASCII_SMALL_A,
1747 ASCII_SMALL_A,
1748 UTF8_PILLCROW[0],
1749 UTF8_PILLCROW[1],
1750 ASCII_SMALL_A,
1751 ASCII_SMALL_A
1752 ]
1753 );
1754 }
1755 let v2 = vec![b'a', b'a', b'a', b'a', b'z'];
1756 let s = from_latin1(v2.clone());
1757 {
1758 let no_gc = unsafe { NoGC::new() };
1759 assert_eq!(
1760 *s.as_bytes(&no_gc),
1761 [
1762 ASCII_SMALL_A,
1763 ASCII_SMALL_A,
1764 ASCII_SMALL_A,
1765 ASCII_SMALL_A,
1766 ASCII_SMALL_Z
1767 ]
1768 );
1769 }
1770
1771 let str = "abc%$²".to_owned();
1772 let s = DOMString::from(str.clone());
1773 {
1774 let no_gc = unsafe { NoGC::new() };
1775 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1776 }
1777 let str = "AbBcC❤&%$#".to_owned();
1778 let s = DOMString::from(str.clone());
1779 {
1780 let no_gc = unsafe { NoGC::new() };
1781 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1782 }
1783 let s = DOMString::from_static("AbBcC❤&%$#");
1784
1785 {
1786 let no_gc = unsafe { NoGC::new() };
1787 assert_eq!(&*s.as_bytes(&no_gc), str.as_bytes());
1788 }
1789 }
1790}