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