use dom_struct::dom_struct;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
use crate::dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId, TextTypeId};
use crate::dom::bindings::codegen::UnionTypes::NodeOrString;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::cdatasection::CDATASection;
use crate::dom::comment::Comment;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::mutationobserver::{Mutation, MutationObserver};
use crate::dom::node::{ChildrenMutation, Node, NodeDamage};
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::dom::text::Text;
use crate::dom::virtualmethods::vtable_for;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct CharacterData {
node: Node,
data: DomRefCell<DOMString>,
}
impl CharacterData {
pub fn new_inherited(data: DOMString, document: &Document) -> CharacterData {
CharacterData {
node: Node::new_inherited(document),
data: DomRefCell::new(data),
}
}
pub fn clone_with_data(
&self,
data: DOMString,
document: &Document,
can_gc: CanGc,
) -> DomRoot<Node> {
match self.upcast::<Node>().type_id() {
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
DomRoot::upcast(Comment::new(data, document, None, can_gc))
},
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
let pi = self.downcast::<ProcessingInstruction>().unwrap();
DomRoot::upcast(ProcessingInstruction::new(
pi.Target(),
data,
document,
can_gc,
))
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::CDATASection)) => {
DomRoot::upcast(CDATASection::new(data, document, can_gc))
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => {
DomRoot::upcast(Text::new(data, document, can_gc))
},
_ => unreachable!(),
}
}
#[inline]
pub fn data(&self) -> Ref<DOMString> {
self.data.borrow()
}
#[inline]
pub fn append_data(&self, data: &str) {
self.queue_mutation_record();
self.data.borrow_mut().push_str(data);
self.content_changed();
}
fn content_changed(&self) {
let node = self.upcast::<Node>();
node.dirty(NodeDamage::OtherNodeDamage);
if self.is::<Text>() {
if let Some(parent_node) = node.GetParentNode() {
let mutation = ChildrenMutation::ChangeText;
vtable_for(&parent_node).children_changed(&mutation);
}
}
}
fn queue_mutation_record(&self) {
let mutation = Mutation::CharacterData {
old_value: self.data.borrow().clone(),
};
MutationObserver::queue_a_mutation_record(self.upcast::<Node>(), mutation);
}
}
impl CharacterDataMethods for CharacterData {
fn Data(&self) -> DOMString {
self.data.borrow().clone()
}
fn SetData(&self, data: DOMString) {
self.queue_mutation_record();
let old_length = self.Length();
let new_length = data.encode_utf16().count() as u32;
*self.data.borrow_mut() = data;
self.content_changed();
let node = self.upcast::<Node>();
node.ranges()
.replace_code_units(node, 0, old_length, new_length);
}
fn Length(&self) -> u32 {
self.data.borrow().encode_utf16().count() as u32
}
fn SubstringData(&self, offset: u32, count: u32) -> Fallible<DOMString> {
let replace_surrogates = self
.upcast::<Node>()
.owner_doc()
.window()
.replace_surrogates();
let data = self.data.borrow();
let mut substring = String::new();
let remaining = match split_at_utf16_code_unit_offset(&data, offset, replace_surrogates) {
Ok((_, astral, s)) => {
if astral.is_some() {
substring += "\u{FFFD}";
}
s
},
Err(()) => return Err(Error::IndexSize),
};
match split_at_utf16_code_unit_offset(remaining, count, replace_surrogates) {
Err(()) => substring += remaining,
Ok((s, astral, _)) => {
substring += s;
if astral.is_some() {
substring += "\u{FFFD}";
}
},
};
Ok(DOMString::from(substring))
}
fn AppendData(&self, data: DOMString) {
self.append_data(&data);
}
fn InsertData(&self, offset: u32, arg: DOMString) -> ErrorResult {
self.ReplaceData(offset, 0, arg)
}
fn DeleteData(&self, offset: u32, count: u32) -> ErrorResult {
self.ReplaceData(offset, count, DOMString::new())
}
fn ReplaceData(&self, offset: u32, count: u32, arg: DOMString) -> ErrorResult {
let mut new_data;
{
let replace_surrogates = self
.upcast::<Node>()
.owner_doc()
.window()
.replace_surrogates();
let data = self.data.borrow();
let prefix;
let replacement_before;
let remaining;
match split_at_utf16_code_unit_offset(&data, offset, replace_surrogates) {
Ok((p, astral, r)) => {
prefix = p;
replacement_before = if astral.is_some() { "\u{FFFD}" } else { "" };
remaining = r;
},
Err(()) => return Err(Error::IndexSize),
};
let replacement_after;
let suffix;
match split_at_utf16_code_unit_offset(remaining, count, replace_surrogates) {
Err(()) => {
replacement_after = "";
suffix = "";
},
Ok((_, astral, s)) => {
replacement_after = if astral.is_some() { "\u{FFFD}" } else { "" };
suffix = s;
},
};
self.queue_mutation_record();
new_data = String::with_capacity(
prefix.len() +
replacement_before.len() +
arg.len() +
replacement_after.len() +
suffix.len(),
);
new_data.push_str(prefix);
new_data.push_str(replacement_before);
new_data.push_str(&arg);
new_data.push_str(replacement_after);
new_data.push_str(suffix);
}
*self.data.borrow_mut() = DOMString::from(new_data);
self.content_changed();
let node = self.upcast::<Node>();
node.ranges()
.replace_code_units(node, offset, count, arg.encode_utf16().count() as u32);
Ok(())
}
fn Before(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().before(nodes, can_gc)
}
fn After(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().after(nodes, can_gc)
}
fn ReplaceWith(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
self.upcast::<Node>().replace_with(nodes, can_gc)
}
fn Remove(&self) {
let node = self.upcast::<Node>();
node.remove_self();
}
fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>()
.preceding_siblings()
.filter_map(DomRoot::downcast)
.next()
}
fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> {
self.upcast::<Node>()
.following_siblings()
.filter_map(DomRoot::downcast)
.next()
}
}
pub trait LayoutCharacterDataHelpers<'dom> {
fn data_for_layout(self) -> &'dom str;
}
impl<'dom> LayoutCharacterDataHelpers<'dom> for LayoutDom<'dom, CharacterData> {
#[allow(unsafe_code)]
#[inline]
fn data_for_layout(self) -> &'dom str {
unsafe { self.unsafe_get().data.borrow_for_layout() }
}
}
fn split_at_utf16_code_unit_offset(
s: &str,
offset: u32,
replace_surrogates: bool,
) -> Result<(&str, Option<char>, &str), ()> {
let mut code_units = 0;
for (i, c) in s.char_indices() {
if code_units == offset {
let (a, b) = s.split_at(i);
return Ok((a, None, b));
}
code_units += 1;
if c > '\u{FFFF}' {
if code_units == offset {
if replace_surrogates {
debug_assert_eq!(c.len_utf8(), 4);
return Ok((&s[..i], Some(c), &s[i + c.len_utf8()..]));
}
panic!(
"\n\n\
Would split a surrogate pair in CharacterData API.\n\
If you see this in real content, please comment with the URL\n\
on https://github.com/servo/servo/issues/6873\n\
\n"
);
}
code_units += 1;
}
}
if code_units == offset {
Ok((s, None, ""))
} else {
Err(())
}
}