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;
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
43unsafe 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#[derive(Debug)]
64pub enum EncodedBytes<'a> {
65 Latin1(Ref<'a, [u8]>),
67 Utf8(Ref<'a, [u8]>),
69}
70
71impl EncodedBytes<'_> {
72 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 pub fn is_empty(&self) -> bool {
93 self.bytes().is_empty()
94 }
95}
96
97#[derive(Zeroize)]
98enum DOMStringType {
99 Rust(String),
101 #[zeroize(skip)]
103 JSString(RootedTraceableBox<Heap<*mut JSString>>),
104 #[cfg(test)]
105 Latin1Vec(Vec<u8>),
108 #[zeroize(skip)] RustStatic(&'static str),
110}
111
112impl Default for DOMStringType {
113 fn default() -> Self {
114 Self::Rust(Default::default())
115 }
116}
117
118impl DOMStringType {
119 fn as_raw_bytes(&self) -> &[u8] {
124 match self {
125 DOMStringType::Rust(s) => s.as_bytes(),
126 DOMStringType::JSString(rooted_traceable_box) => unsafe {
127 get_latin1_string_bytes(rooted_traceable_box)
128 },
129 #[cfg(test)]
130 DOMStringType::Latin1Vec(items) => items,
131 DOMStringType::RustStatic(s) => s.as_bytes(),
132 }
133 }
134
135 fn ensure_rust_string(&mut self) -> &mut String {
136 let new_string = match self {
137 DOMStringType::Rust(string) => return string,
138 DOMStringType::JSString(rooted_traceable_box) => {
139 let cx = unsafe { JSContext::get_from_thread() };
140 let cx = cx.as_ref().expect("JS runtime has shut down");
141 unsafe { jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap()) }
142 },
143 #[cfg(test)]
144 DOMStringType::Latin1Vec(items) => {
145 let mut v = vec![0; items.len() * 2];
146 let real_size =
147 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
148 v.truncate(real_size);
149
150 unsafe { String::from_utf8_unchecked(v) }
153 },
154 DOMStringType::RustStatic(s) => s.to_owned(),
156 };
157 *self = DOMStringType::Rust(new_string);
158 self.ensure_rust_string()
159 }
160}
161
162#[derive(Debug)]
165pub struct StringView<'a>(Ref<'a, str>);
166
167impl StringView<'_> {
168 pub fn split_html_space_characters(&self) -> impl Iterator<Item = &str> {
169 self.split(HTML_SPACE_CHARACTERS)
170 .filter(|string| !string.is_empty())
171 }
172}
173
174impl From<StringView<'_>> for String {
175 fn from(string_view: StringView<'_>) -> Self {
176 string_view.0.to_string()
177 }
178}
179
180impl Deref for StringView<'_> {
181 type Target = str;
182 fn deref(&self) -> &str {
183 &(self.0)
184 }
185}
186
187impl AsRef<str> for StringView<'_> {
188 fn as_ref(&self) -> &str {
189 &(self.0)
190 }
191}
192
193impl PartialEq for StringView<'_> {
194 fn eq(&self, other: &Self) -> bool {
195 self.0.eq(&*(other.0))
196 }
197}
198
199impl PartialEq<&str> for StringView<'_> {
200 fn eq(&self, other: &&str) -> bool {
201 self.0.eq(*other)
202 }
203}
204
205impl Eq for StringView<'_> {}
206
207impl PartialOrd for StringView<'_> {
208 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
209 self.0.partial_cmp(&**other)
210 }
211}
212
213impl Ord for StringView<'_> {
214 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
215 self.0.cmp(other)
216 }
217}
218
219unsafe impl Trace for DOMStringType {
225 unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
226 unsafe {
227 match self {
228 DOMStringType::Rust(_s) => {},
229 DOMStringType::JSString(rooted_traceable_box) => rooted_traceable_box.trace(tracer),
230 #[cfg(test)]
231 DOMStringType::Latin1Vec(_s) => {},
232 DOMStringType::RustStatic(_) => {},
233 }
234 }
235 }
236}
237
238impl malloc_size_of::MallocSizeOf for DOMStringType {
239 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
240 match self {
241 DOMStringType::Rust(s) => s.size_of(ops),
242 DOMStringType::JSString(_rooted_traceable_box) => {
243 0
245 },
246 #[cfg(test)]
247 DOMStringType::Latin1Vec(s) => s.size_of(ops),
248 DOMStringType::RustStatic(_s) => 0,
249 }
250 }
251}
252
253impl std::fmt::Debug for DOMStringType {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 match self {
256 DOMStringType::Rust(s) => f.debug_struct("DOMString").field("rust_string", s).finish(),
257 DOMStringType::JSString(_rooted_traceable_box) => f.debug_struct("DOMString").finish(),
258 #[cfg(test)]
259 DOMStringType::Latin1Vec(s) => f
260 .debug_struct("DOMString")
261 .field("latin1_string", s)
262 .finish(),
263 DOMStringType::RustStatic(s) => f
264 .debug_struct("DOMString")
265 .field("static_string", s)
266 .finish(),
267 }
268 }
269}
270
271#[repr(transparent)]
307#[derive(Debug, Default, MallocSizeOf, JSTraceable)]
308pub struct DOMString(RefCell<DOMStringType>);
309
310impl Clone for DOMString {
311 fn clone(&self) -> Self {
312 self.ensure_rust_string().clone().into()
313 }
314}
315
316pub enum DOMStringErrorType {
317 JSConversionError,
318}
319
320impl DOMString {
321 pub fn new() -> DOMString {
323 Default::default()
324 }
325
326 pub fn from_js_string(
329 cx: &mut JSContext,
330 value: HandleValue,
331 ) -> Result<DOMString, DOMStringErrorType> {
332 let string_ptr = unsafe { js::rust::ToString(cx, value) };
333 if string_ptr.is_null() {
334 debug!("ToString failed");
335 Err(DOMStringErrorType::JSConversionError)
336 } else {
337 let latin1 = unsafe { js::jsapi::JS_DeprecatedStringHasLatin1Chars(string_ptr) };
338 let inner = if latin1 {
339 let h = RootedTraceableBox::from_box(Heap::boxed(string_ptr));
340 DOMStringType::JSString(h)
341 } else {
342 DOMStringType::Rust(unsafe {
344 jsstr_to_string(cx, NonNull::new(string_ptr).unwrap())
345 })
346 };
347 Ok(DOMString(RefCell::new(inner)))
348 }
349 }
350
351 pub fn from_static(s: &'static str) -> DOMString {
353 DOMString(RefCell::new(DOMStringType::RustStatic(s)))
354 }
355
356 fn ensure_rust_string(&self) -> RefMut<'_, String> {
359 let inner = self.0.borrow_mut();
360 RefMut::map(inner, |inner| inner.ensure_rust_string())
361 }
362
363 #[expect(unused)]
365 fn debug_js(&self, cx: &JSContext) {
366 match *self.0.borrow() {
367 DOMStringType::Rust(ref s) => info!("Rust String ({})", s),
368 DOMStringType::JSString(ref rooted_traceable_box) => {
369 let s = unsafe {
370 jsstr_to_string(cx, NonNull::new(rooted_traceable_box.get()).unwrap())
371 };
372 info!("JSString ({})", s);
373 },
374 #[cfg(test)]
375 DOMStringType::Latin1Vec(ref items) => info!("Latin1 string"),
376 DOMStringType::RustStatic(s) => info!("Static Rust String ({})", s),
377 }
378 }
379
380 pub fn str(&self) -> StringView<'_> {
382 {
383 let inner = self.0.borrow();
384 if matches!(&*inner, DOMStringType::Rust(..)) {
385 return StringView(Ref::map(inner, |inner| match inner {
386 DOMStringType::Rust(string) => string.as_str(),
387 _ => unreachable!("Guaranteed by condition above"),
388 }));
389 }
390 }
391
392 self.ensure_rust_string();
393 self.str()
394 }
395
396 pub fn encoded_bytes(&self) -> EncodedBytes<'_> {
399 let inner = self.0.borrow();
400 match &*inner {
401 DOMStringType::Rust(..) | DOMStringType::RustStatic(..) => {
402 EncodedBytes::Utf8(Ref::map(inner, |inner| inner.as_raw_bytes()))
403 },
404 DOMStringType::JSString(..) => {
405 EncodedBytes::Latin1(Ref::map(inner, |inner| inner.as_raw_bytes()))
406 },
407 #[cfg(test)]
408 DOMStringType::Latin1Vec(..) => {
409 EncodedBytes::Latin1(Ref::map(inner, |inner| inner.as_raw_bytes()))
410 },
411 }
412 }
413
414 pub fn clear(&mut self) {
415 let mut inner = self.0.borrow_mut();
416 let DOMStringType::Rust(string) = &mut *inner else {
417 *inner = DOMStringType::Rust(String::new());
418 return;
419 };
420 string.clear();
421 }
422
423 pub fn is_empty(&self) -> bool {
424 self.encoded_bytes().is_empty()
425 }
426
427 pub fn len(&self) -> usize {
432 self.encoded_bytes().len()
433 }
434
435 pub fn len_utf8(&self) -> Utf8CodeUnits {
442 Utf8CodeUnits(self.len())
443 }
444
445 pub fn len_utf16(&self) -> Utf16CodeUnits {
450 Utf16CodeUnits(self.str().chars().map(char::len_utf16).sum())
451 }
452
453 pub fn make_ascii_lowercase(&mut self) {
456 self.0
457 .borrow_mut()
458 .ensure_rust_string()
459 .make_ascii_lowercase();
460 }
461
462 pub fn push_str(&mut self, string_to_push: &str) {
463 self.0
464 .borrow_mut()
465 .ensure_rust_string()
466 .push_str(string_to_push);
467 }
468
469 pub fn strip_leading_and_trailing_ascii_whitespace(&mut self) {
471 if self.is_empty() {
472 return;
473 }
474
475 let mut inner = self.0.borrow_mut();
476 let string = inner.ensure_rust_string();
477 let trailing_whitespace_len = string
478 .trim_end_matches(|character: char| character.is_ascii_whitespace())
479 .len();
480 string.truncate(trailing_whitespace_len);
481 if string.is_empty() {
482 return;
483 }
484
485 let first_non_whitespace = string
486 .find(|character: char| !character.is_ascii_whitespace())
487 .unwrap();
488 string.replace_range(0..first_non_whitespace, "");
489 }
490
491 pub fn is_valid_floating_point_number_string(&self) -> bool {
493 static RE: LazyLock<Regex> = LazyLock::new(|| {
494 Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap()
495 });
496
497 RE.is_match(self.0.borrow_mut().ensure_rust_string()) &&
498 self.parse_floating_point_number().is_some()
499 }
500
501 pub fn parse<T: FromStr>(&self) -> Result<T, <T as FromStr>::Err> {
502 self.str().parse::<T>()
503 }
504
505 pub fn parse_floating_point_number(&self) -> Option<f64> {
507 parse_floating_point_number(&self.str())
508 }
509
510 pub fn set_best_representation_of_the_floating_point_number(&mut self) {
512 if let Some(val) = self.parse_floating_point_number() {
513 let parsed_value = if val.is_zero() { 0.0_f64 } else { val };
515
516 *self.0.borrow_mut() = DOMStringType::Rust(parsed_value.to_string());
517 }
518 }
519
520 pub fn to_lowercase(&self) -> String {
521 self.str().to_lowercase()
522 }
523
524 pub fn to_uppercase(&self) -> String {
525 self.str().to_uppercase()
526 }
527
528 pub fn strip_newlines(&mut self) {
529 self.0
532 .borrow_mut()
533 .ensure_rust_string()
534 .retain(|character| character != '\r' && character != '\n');
535 }
536
537 pub fn normalize_newlines(&mut self) {
539 let mut inner = self.0.borrow_mut();
543 let string = inner.ensure_rust_string();
544 *string = string.replace("\r\n", "\n").replace("\r", "\n")
545 }
546
547 pub fn replace(self, needle: &str, replace_char: &str) -> DOMString {
548 let new_string = self.str().to_owned();
549 DOMString(RefCell::new(DOMStringType::Rust(
550 new_string.replace(needle, replace_char),
551 )))
552 }
553
554 pub fn starts_with(&self, c: char) -> bool {
556 if !c.is_ascii() {
557 self.str().starts_with(c)
558 } else {
559 self.encoded_bytes().bytes().starts_with(&[c as u8])
562 }
563 }
564
565 pub fn starts_with_str(&self, needle: &str) -> bool {
566 self.str().starts_with(needle)
567 }
568
569 pub fn ends_with_str(&self, needle: &str) -> bool {
570 self.str().ends_with(needle)
571 }
572
573 pub fn contains(&self, needle: &str) -> bool {
574 self.str().contains(needle)
575 }
576
577 pub fn eq_ignore_ascii_case(&self, other: &str) -> bool {
582 if other.is_ascii() {
583 self.encoded_bytes()
584 .bytes()
585 .eq_ignore_ascii_case(other.as_bytes())
586 } else {
587 self.str().eq_ignore_ascii_case(other)
588 }
589 }
590
591 pub fn to_ascii_lowercase(&self) -> String {
592 let conversion = match self.encoded_bytes() {
593 EncodedBytes::Latin1(bytes) => {
594 if bytes.iter().all(|c| *c <= ASCII_END) {
595 Some(unsafe {
597 String::from_utf8_unchecked(
598 bytes
599 .iter()
600 .map(|c| {
601 if *c >= ASCII_CAPITAL_A && *c <= ASCII_CAPITAL_Z {
602 c + 32
603 } else {
604 *c
605 }
606 })
607 .collect(),
608 )
609 })
610 } else {
611 None
612 }
613 },
614 EncodedBytes::Utf8(bytes) => unsafe {
615 Some(str::from_utf8_unchecked(&bytes).to_ascii_lowercase())
617 },
618 };
619 if let Some(conversion) = conversion {
621 conversion
622 } else {
623 self.str().to_ascii_lowercase()
624 }
625 }
626
627 fn contains_space_characters(
628 &self,
629 latin1_characters: &'static [u8],
630 utf8_characters: &'static [char],
631 ) -> bool {
632 match self.encoded_bytes() {
633 EncodedBytes::Latin1(items) => {
634 latin1_characters.iter().any(|byte| items.contains(byte))
635 },
636 EncodedBytes::Utf8(bytes) => {
637 let s = unsafe { str::from_utf8_unchecked(&bytes) };
639 s.contains(utf8_characters)
640 },
641 }
642 }
643
644 pub fn contains_tab_or_newline(&self) -> bool {
646 const LATIN_TAB_OR_NEWLINE: [u8; 3] = [ASCII_TAB, ASCII_NEWLINE, ASCII_CR];
647 const UTF8_TAB_OR_NEWLINE: [char; 3] = ['\u{0009}', '\u{000a}', '\u{000d}'];
648
649 self.contains_space_characters(&LATIN_TAB_OR_NEWLINE, &UTF8_TAB_OR_NEWLINE)
650 }
651
652 pub fn contains_html_space_characters(&self) -> bool {
654 const SPACE_BYTES: [u8; 5] = [
655 ASCII_TAB,
656 ASCII_NEWLINE,
657 ASCII_FORMFEED,
658 ASCII_CR,
659 ASCII_SPACE,
660 ];
661 self.contains_space_characters(&SPACE_BYTES, HTML_SPACE_CHARACTERS)
662 }
663
664 pub fn as_bytes(&self) -> BytesView<'_> {
666 if self.is_ascii() {
670 BytesView(self.0.borrow())
671 } else {
672 self.ensure_rust_string();
673 BytesView(self.0.borrow())
674 }
675 }
676
677 pub fn is_ascii_lowercase(&self) -> bool {
679 match self.encoded_bytes() {
680 EncodedBytes::Latin1(items) => items
681 .iter()
682 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(c)),
683 EncodedBytes::Utf8(s) => s
684 .iter()
685 .map(|c| c.to_u8().unwrap_or(ASCII_LOWERCASE_A - 1))
686 .all(|c| (ASCII_LOWERCASE_A..=ASCII_LOWERCASE_Z).contains(&c)),
687 }
688 }
689
690 pub fn is_ascii(&self) -> bool {
692 self.encoded_bytes().bytes().is_ascii()
693 }
694
695 pub fn is_valid_for_cookie(&self) -> bool {
699 match self.encoded_bytes() {
700 EncodedBytes::Latin1(items) | EncodedBytes::Utf8(items) => !items
701 .iter()
702 .any(|c| *c == 0x7f || (*c <= 0x1f && *c != 0x09)),
703 }
704 }
705
706 fn with_str_reference<Result>(&self, callback: fn(&str) -> Result) -> Result {
709 match self.encoded_bytes() {
710 EncodedBytes::Latin1(latin1_bytes) => {
712 if latin1_bytes.iter().all(|character| character.is_ascii()) {
713 return callback(unsafe { str::from_utf8_unchecked(&latin1_bytes) });
716 }
717 },
718 EncodedBytes::Utf8(utf8_bytes) => {
719 return callback(unsafe { str::from_utf8_unchecked(&utf8_bytes) });
722 },
723 };
724 callback(self.str().deref())
725 }
726
727 pub fn normalize_crlf(&self) -> String {
735 let s = self.str();
736 let mut buf = String::new();
737 let mut prev = ' ';
738 for ch in s.chars() {
739 match ch {
740 '\n' if prev != '\r' => {
741 buf.push('\r');
742 buf.push('\n');
743 },
744 '\n' => {
745 buf.push('\n');
746 },
747 _ if prev == '\r' => {
750 buf.push('\n');
751 buf.push(ch);
752 },
753 _ => buf.push(ch),
754 };
755 prev = ch;
756 }
757 if prev == '\r' {
759 buf.push('\n');
760 }
761 buf
762 }
763}
764
765pub fn parse_floating_point_number(input: &str) -> Option<f64> {
767 input.trim().parse::<f64>().ok().filter(|value| {
773 !(value.is_infinite() || value.is_nan() || input.ends_with('.') || input.starts_with('+'))
776 })
777}
778
779pub struct BytesView<'a>(Ref<'a, DOMStringType>);
780
781impl Deref for BytesView<'_> {
782 type Target = [u8];
783
784 fn deref(&self) -> &Self::Target {
785 self.0.as_raw_bytes()
787 }
788}
789
790impl Ord for DOMString {
791 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
792 self.str().cmp(&other.str())
793 }
794}
795
796impl PartialOrd for DOMString {
797 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
798 self.str().partial_cmp(&other.str())
799 }
800}
801
802impl Extend<char> for DOMString {
803 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
804 self.0.borrow_mut().ensure_rust_string().extend(iter)
805 }
806}
807
808impl ToJSValConvertible for DOMString {
809 fn safe_to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
810 let val = self.0.borrow();
811 match *val {
812 DOMStringType::Rust(ref s) => s.safe_to_jsval(cx, rval),
813 DOMStringType::JSString(ref rooted_traceable_box) => unsafe {
814 rval.set(StringValue(&*rooted_traceable_box.get()));
815 },
816 #[cfg(test)]
817 DOMStringType::Latin1Vec(ref items) => {
818 let mut v = vec![0; items.len() * 2];
819 let real_size =
820 encoding_rs::mem::convert_latin1_to_utf8(items.as_slice(), v.as_mut_slice());
821 v.truncate(real_size);
822
823 String::from_utf8(v)
824 .expect("Error in constructin test string")
825 .safe_to_jsval(cx, rval);
826 },
827 DOMStringType::RustStatic(s) => s.safe_to_jsval(cx, rval),
828 };
829 }
830}
831
832impl std::hash::Hash for DOMString {
833 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
834 self.str().hash(state);
835 }
836}
837
838impl std::fmt::Display for DOMString {
839 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
840 fmt::Display::fmt(self.str().deref(), f)
841 }
842}
843
844impl std::cmp::PartialEq<str> for DOMString {
845 fn eq(&self, other: &str) -> bool {
846 if other.is_ascii() {
847 *other.as_bytes() == *self.encoded_bytes().bytes()
848 } else {
849 self.str().deref() == other
850 }
851 }
852}
853
854impl std::cmp::PartialEq<&str> for DOMString {
855 fn eq(&self, other: &&str) -> bool {
856 self.eq(*other)
857 }
858}
859
860impl std::cmp::PartialEq<String> for DOMString {
861 fn eq(&self, other: &String) -> bool {
862 self.eq(other.as_str())
863 }
864}
865
866impl std::cmp::PartialEq<DOMString> for String {
867 fn eq(&self, other: &DOMString) -> bool {
868 other.eq(self)
869 }
870}
871
872impl std::cmp::PartialEq<DOMString> for str {
873 fn eq(&self, other: &DOMString) -> bool {
874 other.eq(self)
875 }
876}
877
878impl std::cmp::PartialEq for DOMString {
879 fn eq(&self, other: &DOMString) -> bool {
880 let result = match (self.encoded_bytes(), other.encoded_bytes()) {
881 (EncodedBytes::Latin1(bytes), EncodedBytes::Latin1(other_bytes)) => {
882 Some(*bytes == *other_bytes)
883 },
884 (EncodedBytes::Latin1(bytes), EncodedBytes::Utf8(other_bytes))
885 if other_bytes.is_ascii() =>
886 {
887 Some(*bytes == *other_bytes)
888 },
889 (EncodedBytes::Utf8(bytes), EncodedBytes::Latin1(other_bytes)) if bytes.is_ascii() => {
890 Some(*bytes == *other_bytes)
891 },
892 (EncodedBytes::Utf8(bytes), EncodedBytes::Utf8(other_bytes)) => {
893 Some(*bytes == *other_bytes)
894 },
895 _ => None,
896 };
897
898 if let Some(eq_result) = result {
899 return eq_result;
900 }
901
902 *self.str() == *other.str()
903 }
904}
905
906impl std::cmp::Eq for DOMString {}
907
908impl From<std::string::String> for DOMString {
909 fn from(string: String) -> Self {
910 DOMString(RefCell::new(DOMStringType::Rust(string)))
911 }
912}
913
914impl From<&str> for DOMString {
916 fn from(string: &str) -> Self {
917 String::from(string).into()
918 }
919}
920
921impl From<DOMString> for LocalName {
922 fn from(dom_string: DOMString) -> LocalName {
923 dom_string.with_str_reference(|string| LocalName::from(string))
924 }
925}
926
927impl From<&DOMString> for LocalName {
928 fn from(dom_string: &DOMString) -> LocalName {
929 dom_string.with_str_reference(|string| LocalName::from(string))
930 }
931}
932
933impl From<DOMString> for Namespace {
934 fn from(dom_string: DOMString) -> Namespace {
935 dom_string.with_str_reference(|string| Namespace::from(string))
936 }
937}
938
939impl From<DOMString> for Atom {
940 fn from(dom_string: DOMString) -> Atom {
941 dom_string.with_str_reference(|string| Atom::from(string))
942 }
943}
944
945impl From<DOMString> for String {
946 fn from(val: DOMString) -> Self {
947 val.ensure_rust_string();
948 let inner = val.0.take();
949 match inner {
950 DOMStringType::Rust(s) => s,
951 DOMStringType::JSString(_) => unreachable!(),
952 #[cfg(test)]
953 DOMStringType::Latin1Vec(items) => String::from_utf8(items).expect("Not valid latin1"),
954 DOMStringType::RustStatic(s) => s.to_owned(),
955 }
956 }
957}
958
959impl From<DOMString> for Vec<u8> {
960 fn from(value: DOMString) -> Self {
961 value.ensure_rust_string();
962 let inner = value.0.take();
963 match inner {
964 DOMStringType::Rust(s) => s.into_bytes(),
965 DOMStringType::JSString(_) => unreachable!(),
966 #[cfg(test)]
967 DOMStringType::Latin1Vec(items) => items,
968 DOMStringType::RustStatic(_) => unreachable!(),
969 }
970 }
971}
972
973impl From<Cow<'_, str>> for DOMString {
974 fn from(value: Cow<'_, str>) -> Self {
975 DOMString(RefCell::new(DOMStringType::Rust(value.into_owned())))
976 }
977}
978
979impl Zeroize for DOMString {
980 fn zeroize(&mut self) {
981 self.0.get_mut().zeroize();
982 }
983}
984
985#[macro_export]
986macro_rules! match_domstring_ascii_inner {
987 ($variant: expr, $input: expr, $ascii_literal: literal => $then: expr, $($rest:tt)*) => {
988 if {
989 debug_assert!(($ascii_literal).is_ascii());
990 $ascii_literal.as_bytes()
991 } == $input.bytes() {
992 $then
993 } else {
994 $crate::match_domstring_ascii_inner!($variant, $input, $($rest)*)
995 }
996
997 };
998 ($variant: expr, $input: expr, $p: pat => $then: expr,) => {
999 match $input {
1000 $p => $then
1001 }
1002 }
1003}
1004
1005#[macro_export]
1022macro_rules! match_domstring_ascii {
1023 ($input:expr, $($tail:tt)*) => {
1024 {
1025 use $crate::domstring::EncodedBytes;
1026
1027 let encoded_bytes = $input.encoded_bytes();
1028 match encoded_bytes {
1029 EncodedBytes::Latin1(_) => {
1030 $crate::match_domstring_ascii_inner!(EncodedBytes::Latin1, encoded_bytes, $($tail)*)
1031 }
1032 EncodedBytes::Utf8(_) => {
1033 $crate::match_domstring_ascii_inner!(EncodedBytes::Utf8, encoded_bytes, $($tail)*)
1034 }
1035
1036 }
1037 }
1038 };
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043 use super::*;
1044
1045 const LATIN1_PILLCROW: u8 = 0xB6;
1046 const UTF8_PILLCROW: [u8; 2] = [194, 182];
1047 const LATIN1_POWER2: u8 = 0xB2;
1048
1049 fn from_latin1(l1vec: Vec<u8>) -> DOMString {
1050 DOMString(RefCell::new(DOMStringType::Latin1Vec(l1vec)))
1051 }
1052
1053 #[test]
1054 fn string_functions() {
1055 let s = DOMString::from("AbBcC❤&%$#");
1056 let s_copy = s.clone();
1057 assert_eq!(s.to_ascii_lowercase(), "abbcc❤&%$#");
1058 assert_eq!(s, s_copy);
1059 assert_eq!(s.len(), 12);
1060 assert_eq!(s_copy.len(), 12);
1061 assert!(s.starts_with('A'));
1062 let s2 = DOMString::from("");
1063 assert!(s2.is_empty());
1064 }
1065
1066 #[test]
1067 fn string_functions_latin1() {
1068 {
1069 let s = from_latin1(vec![
1070 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1071 ]);
1072 assert_eq!(s.to_ascii_lowercase(), "abbcc&%$#²");
1073 }
1074 {
1075 let s = from_latin1(vec![b'A', b'b', b'B', b'c', b'C']);
1076 assert_eq!(s.to_ascii_lowercase(), "abbcc");
1077 }
1078 {
1079 let s = from_latin1(vec![
1080 b'A', b'b', b'B', b'c', b'C', b'&', b'%', b'$', b'#', 0xB2,
1081 ]);
1082 assert_eq!(s.len(), 11);
1083 assert!(s.starts_with('A'));
1084 }
1085 {
1086 let s = from_latin1(vec![]);
1087 assert!(s.is_empty());
1088 }
1089 }
1090
1091 #[test]
1092 fn test_length() {
1093 let s1 = from_latin1(vec![
1094 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,
1095 0xAE, 0xAF,
1096 ]);
1097 let s2 = from_latin1(vec![
1098 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
1099 0xBE, 0xBF,
1100 ]);
1101 let s3 = from_latin1(vec![
1102 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
1103 0xCE, 0xCF,
1104 ]);
1105 let s4 = from_latin1(vec![
1106 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
1107 0xDE, 0xDF,
1108 ]);
1109 let s5 = from_latin1(vec![
1110 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
1111 0xEE, 0xEF,
1112 ]);
1113 let s6 = from_latin1(vec![
1114 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
1115 0xFE, 0xFF,
1116 ]);
1117
1118 let s1_utf8 = String::from("\u{00A0}¡¢£¤¥¦§¨©ª«¬\u{00AD}®¯");
1119 let s2_utf8 = String::from("°±²³´µ¶·¸¹º»¼½¾¿");
1120 let s3_utf8 = String::from("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
1121 let s4_utf8 = String::from("ÐÑÒÓÔÕÖרÙÚÛÜÝÞß");
1122 let s5_utf8 = String::from("àáâãäåæçèéêëìíîï");
1123 let s6_utf8 = String::from("ðñòóôõö÷øùúûüýþÿ");
1124
1125 assert_eq!(s1.len(), s1_utf8.len());
1126 assert_eq!(s2.len(), s2_utf8.len());
1127 assert_eq!(s3.len(), s3_utf8.len());
1128 assert_eq!(s4.len(), s4_utf8.len());
1129 assert_eq!(s5.len(), s5_utf8.len());
1130 assert_eq!(s6.len(), s6_utf8.len());
1131
1132 s1.ensure_rust_string();
1133 s2.ensure_rust_string();
1134 s3.ensure_rust_string();
1135 s4.ensure_rust_string();
1136 s5.ensure_rust_string();
1137 s6.ensure_rust_string();
1138 assert_eq!(s1.len(), s1_utf8.len());
1139 assert_eq!(s2.len(), s2_utf8.len());
1140 assert_eq!(s3.len(), s3_utf8.len());
1141 assert_eq!(s4.len(), s4_utf8.len());
1142 assert_eq!(s5.len(), s5_utf8.len());
1143 assert_eq!(s6.len(), s6_utf8.len());
1144 }
1145
1146 #[test]
1147 fn test_convert() {
1148 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1149 s.ensure_rust_string();
1150 assert_eq!(&*s.str(), "abc%$");
1151 }
1152
1153 #[test]
1154 fn partial_eq() {
1155 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1156 let string = String::from("abc%$");
1157 let s2 = DOMString::from(string.clone());
1158 let s3 = DOMString::from_static("abc%$");
1159 assert_eq!(s, s2);
1160 assert_eq!(s, string);
1161 assert_eq!(s, s3);
1162 }
1163
1164 #[test]
1165 fn encoded_latin1_bytes() {
1166 let original_latin1_bytes = vec![b'a', b'b', b'c', b'%', b'$', 0xB2];
1167 let dom_string = from_latin1(original_latin1_bytes.clone());
1168 let string_latin1_bytes = match dom_string.encoded_bytes() {
1169 EncodedBytes::Latin1(bytes) => bytes,
1170 _ => unreachable!("Expected Latin1 encoded bytes"),
1171 };
1172 assert_eq!(*original_latin1_bytes, *string_latin1_bytes);
1173 }
1174
1175 #[test]
1176 fn testing_stringview() {
1177 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1178
1179 assert_eq!(
1180 s.str().chars().collect::<Vec<char>>(),
1181 vec!['a', 'b', 'c', '%', '$', '²']
1182 );
1183 assert_eq!(s.str().as_bytes(), String::from("abc%$²").as_bytes());
1184 }
1185
1186 #[test]
1191 fn test_hash() {
1192 use std::hash::{DefaultHasher, Hash, Hasher};
1193 fn hash_value(d: &DOMString) -> u64 {
1194 let mut hasher = DefaultHasher::new();
1195 d.hash(&mut hasher);
1196 hasher.finish()
1197 }
1198
1199 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1200 let s_converted = from_latin1(vec![b'a', b'b', b'c', b'%', b'$', 0xB2]);
1201 s_converted.ensure_rust_string();
1202 let s2 = DOMString::from("abc%$²");
1203 let s3 = DOMString::from_static("abc%$²");
1204
1205 let hash_s = hash_value(&s);
1206 let hash_s_converted = hash_value(&s_converted);
1207 let hash_s2 = hash_value(&s2);
1208 let hash_s3 = hash_value(&s3);
1209
1210 assert_eq!(hash_s, hash_s2);
1211 assert_eq!(hash_s, hash_s_converted);
1212 assert_eq!(hash_s, hash_s3);
1213 }
1214
1215 #[test]
1217 fn test_match_executing() {
1218 {
1220 let s = from_latin1(vec![b'a', b'b', b'c']);
1221 match_domstring_ascii!( s,
1222 "abc" => assert!(true),
1223 "bcd" => assert!(false),
1224 _ => (),
1225 );
1226 }
1227
1228 {
1229 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1230 match_domstring_ascii!( s,
1231 "abc/" => assert!(true),
1232 "bcd" => assert!(false),
1233 _ => (),
1234 );
1235 }
1236
1237 {
1238 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1239 match_domstring_ascii!( s,
1240 "bcd" => assert!(false),
1241 "abc%$" => assert!(true),
1242 _ => (),
1243 );
1244 }
1245
1246 {
1247 let s = DOMString::from("abcde");
1248 match_domstring_ascii!( s,
1249 "abc" => assert!(false),
1250 "bcd" => assert!(false),
1251 _ => assert!(true),
1252 );
1253 }
1254 {
1255 let s = DOMString::from("abc%$");
1256 match_domstring_ascii!( s,
1257 "bcd" => assert!(false),
1258 "abc%$" => assert!(true),
1259 _ => (),
1260 );
1261 }
1262 {
1263 let s = from_latin1(vec![b'a', b'b', b'c']);
1264 match_domstring_ascii!( s,
1265 "abcdd" => assert!(false),
1266 "bcd" => assert!(false),
1267 _ => (),
1268 );
1269 }
1270 {
1271 let s = DOMString::from_static("abc");
1272 match_domstring_ascii!( s,
1273 "abc" => assert!(true),
1274 "bcd" => assert!(false),
1275 _ => (),
1276 );
1277 }
1278 }
1279
1280 #[test]
1282 fn test_match_returning_result() {
1283 {
1284 let s = from_latin1(vec![b'a', b'b', b'c']);
1285 let res = match_domstring_ascii!( s,
1286 "abc" => true,
1287 "bcd" => false,
1288 _ => false,
1289 );
1290 assert_eq!(res, true);
1291 }
1292 {
1293 let s = from_latin1(vec![b'a', b'b', b'c', b'/']);
1294 let res = match_domstring_ascii!( s,
1295 "abc/" => true,
1296 "bcd" => false,
1297 _ => false,
1298 );
1299 assert_eq!(res, true);
1300 }
1301 {
1302 let s = from_latin1(vec![b'a', b'b', b'c', b'%', b'$']);
1303 let res = match_domstring_ascii!( s,
1304 "bcd" => false,
1305 "abc%$" => true,
1306 _ => false,
1307 );
1308 assert_eq!(res, true);
1309 }
1310
1311 {
1312 let s = DOMString::from("abcde");
1313 let res = match_domstring_ascii!( s,
1314 "abc" => false,
1315 "bcd" => false,
1316 _ => true,
1317 );
1318 assert_eq!(res, true);
1319 }
1320 {
1321 let s = DOMString::from("abc%$");
1322 let res = match_domstring_ascii!( s,
1323 "bcd" => false,
1324 "abc%$" => true,
1325 _ => false,
1326 );
1327 assert_eq!(res, true);
1328 }
1329 {
1330 let s = from_latin1(vec![b'a', b'b', b'c']);
1331 let res = match_domstring_ascii!( s,
1332 "abcdd" => false,
1333 "bcd" => false,
1334 _ => true,
1335 );
1336 assert_eq!(res, true);
1337 }
1338 }
1339
1340 #[test]
1341 #[cfg(debug_assertions)]
1342 #[should_panic]
1343 fn test_match_panic() {
1344 let s = DOMString::from("abcd");
1345 let _res = match_domstring_ascii!(s,
1346 "❤" => true,
1347 _ => false,);
1348 }
1349
1350 #[test]
1351 #[cfg(debug_assertions)]
1352 #[should_panic]
1353 fn test_match_panic2() {
1354 let s = DOMString::from("abcd");
1355 let _res = match_domstring_ascii!(s,
1356 "abc" => false,
1357 "❤" => true,
1358 _ => false,
1359 );
1360 }
1361
1362 #[test]
1363 fn test_strip_whitespace() {
1364 {
1365 let mut s = from_latin1(vec![
1366 b' ', b' ', b' ', b'\n', b' ', b'a', b'b', b'c', b'%', b'$', 0xB2, b' ',
1367 ]);
1368
1369 s.strip_leading_and_trailing_ascii_whitespace();
1370 s.ensure_rust_string();
1371 assert_eq!(&*s.str(), "abc%$²");
1372 }
1373 {
1374 let mut s = DOMString::from(" \n abc%$ ");
1375
1376 s.strip_leading_and_trailing_ascii_whitespace();
1377 s.ensure_rust_string();
1378 assert_eq!(&*s.str(), "abc%$");
1379 }
1380 {
1381 let mut s = DOMString::from_static(" \n abc%$ ");
1382
1383 s.strip_leading_and_trailing_ascii_whitespace();
1384 s.ensure_rust_string();
1385 assert_eq!(&*s.str(), "abc%$");
1386 }
1387 }
1388
1389 #[test]
1391 fn contains_html_space_characters() {
1392 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_TAB, b'a', b'a']); assert!(s.contains_html_space_characters());
1394 s.ensure_rust_string();
1395 assert!(s.contains_html_space_characters());
1396
1397 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_NEWLINE, b'a', b'a']); assert!(s.contains_html_space_characters());
1399 s.ensure_rust_string();
1400 assert!(s.contains_html_space_characters());
1401
1402 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_FORMFEED, b'a', b'a']); assert!(s.contains_html_space_characters());
1404 s.ensure_rust_string();
1405 assert!(s.contains_html_space_characters());
1406
1407 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_CR, b'a', b'a']); assert!(s.contains_html_space_characters());
1409 s.ensure_rust_string();
1410 assert!(s.contains_html_space_characters());
1411
1412 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']); assert!(s.contains_html_space_characters());
1414 s.ensure_rust_string();
1415 assert!(s.contains_html_space_characters());
1416
1417 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'a']);
1418 assert!(!s.contains_html_space_characters());
1419 s.ensure_rust_string();
1420 assert!(!s.contains_html_space_characters());
1421
1422 let s = DOMString::from_static("aba aaa");
1423 assert!(s.contains_html_space_characters());
1424 s.ensure_rust_string();
1425 assert!(s.contains_html_space_characters());
1426 }
1427
1428 #[test]
1429 fn atom() {
1430 let s = from_latin1(vec![b'a', b'a', b'a', 0x20, b'a', b'a']);
1431 let atom1 = Atom::from(s);
1432 let s2 = DOMString::from("aaa aa");
1433 let atom2 = Atom::from(s2);
1434 assert_eq!(atom1, atom2);
1435 let s3 = from_latin1(vec![b'a', b'a', b'a', 0xB2, b'a', b'a']);
1436 let atom3 = Atom::from(s3);
1437 assert_ne!(atom1, atom3);
1438 let s3 = DOMString::from_static("aaa\u{03B1}aa");
1439 let atom3 = Atom::from(s3);
1440 assert_ne!(atom1, atom3);
1441 let s4 = DOMString::from_static("aaa aa");
1442 let atom4 = Atom::from(s4);
1443 assert_eq!(atom2, atom4);
1444 }
1445
1446 #[test]
1447 fn namespace() {
1448 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1449 let atom1 = Namespace::from(s);
1450 let s2 = DOMString::from("aaa aa");
1451 let atom2 = Namespace::from(s2);
1452 assert_eq!(atom1, atom2);
1453 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1454 let atom3 = Namespace::from(s3);
1455 assert_ne!(atom1, atom3);
1456 let s4 = DOMString::from_static("aaa aa");
1457 let atom4 = Namespace::from(s4);
1458 assert_eq!(atom2, atom4);
1459 }
1460
1461 #[test]
1462 fn localname() {
1463 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1464 let atom1 = LocalName::from(s);
1465 let s2 = DOMString::from("aaa aa");
1466 let atom2 = LocalName::from(s2);
1467 assert_eq!(atom1, atom2);
1468 let s3 = from_latin1(vec![b'a', b'a', b'a', LATIN1_POWER2, b'a', b'a']);
1469 let atom3 = LocalName::from(s3);
1470 assert_ne!(atom1, atom3);
1471 let s4 = DOMString::from_static("aaa aa");
1472 let atom4 = LocalName::from(s4);
1473 assert_eq!(atom2, atom4);
1474 }
1475
1476 #[test]
1477 fn is_ascii_lowercase() {
1478 let s = from_latin1(vec![b'a', b'a', b'a', ASCII_SPACE, b'a', b'a']);
1479 assert!(!s.is_ascii_lowercase());
1480 let s = from_latin1(vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a']);
1481 assert!(!s.is_ascii_lowercase());
1482 let s = from_latin1(vec![b'a', b'a', b'a', b'a', b'z']);
1483 assert!(s.is_ascii_lowercase());
1484 let s = from_latin1(vec![b'`', b'a', b'a', b'a', b'z']);
1485 assert!(!s.is_ascii_lowercase());
1486 let s = DOMString::from("`aaaz");
1487 assert!(!s.is_ascii_lowercase());
1488 let s = DOMString::from("aaaz");
1489 assert!(s.is_ascii_lowercase());
1490 let s = DOMString::from_static("aaaz");
1491 assert!(s.is_ascii_lowercase());
1492 }
1493
1494 #[test]
1495 fn test_as_bytes() {
1496 const ASCII_SMALL_A: u8 = b'a';
1497 const ASCII_SMALL_Z: u8 = b'z';
1498
1499 let v1 = vec![b'a', b'a', b'a', LATIN1_PILLCROW, b'a', b'a'];
1500 let s = from_latin1(v1.clone());
1501 assert_eq!(
1502 *s.as_bytes(),
1503 [
1504 ASCII_SMALL_A,
1505 ASCII_SMALL_A,
1506 ASCII_SMALL_A,
1507 UTF8_PILLCROW[0],
1508 UTF8_PILLCROW[1],
1509 ASCII_SMALL_A,
1510 ASCII_SMALL_A
1511 ]
1512 );
1513
1514 let v2 = vec![b'a', b'a', b'a', b'a', b'z'];
1515 let s = from_latin1(v2.clone());
1516 assert_eq!(
1517 *s.as_bytes(),
1518 [
1519 ASCII_SMALL_A,
1520 ASCII_SMALL_A,
1521 ASCII_SMALL_A,
1522 ASCII_SMALL_A,
1523 ASCII_SMALL_Z
1524 ]
1525 );
1526
1527 let str = "abc%$²".to_owned();
1528 let s = DOMString::from(str.clone());
1529 assert_eq!(&*s.as_bytes(), str.as_bytes());
1530 let str = "AbBcC❤&%$#".to_owned();
1531 let s = DOMString::from(str.clone());
1532 assert_eq!(&*s.as_bytes(), str.as_bytes());
1533 let s = DOMString::from_static("AbBcC❤&%$#");
1534 assert_eq!(&*s.as_bytes(), str.as_bytes());
1535 }
1536}