mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 16:01:01 +00:00
fix(dom): implement rendered text setters
This commit is contained in:
@@ -2892,7 +2892,6 @@ html/dom/elements/global-attributes/dir-slots-directionality.html
|
||||
html/dom/elements/global-attributes/document-dir.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/dynamic-getter.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/getter.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-setter.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=normal
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=normal&contenteditable=plaintext-only
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=normal&contenteditable=true
|
||||
@@ -2905,7 +2904,6 @@ html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-sp
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=pre-wrap
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=pre-wrap&contenteditable=plaintext-only
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-with-white-spaces.html?white-space=pre-wrap&contenteditable=true
|
||||
html/dom/elements/the-innertext-and-outertext-properties/outertext-setter.html
|
||||
html/dom/partial-updates/tentative/processing-instruction-attributes.html
|
||||
html/dom/partial-updates/tentative/stream-append-has-invalidation.html
|
||||
html/dom/partial-updates/tentative/stream-append-positional-pseudo-class-invalidation.html
|
||||
|
||||
@@ -5902,7 +5902,9 @@ html/dom/elements/global-attributes/translate-non-html-translation-mode.html
|
||||
html/dom/elements/name-content-attribute-and-property.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/getter-first-letter-marker-multicol.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-domnoderemoved-crash.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-setter.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/innertext-whitespace-pre-line.html
|
||||
html/dom/elements/the-innertext-and-outertext-properties/outertext-setter.html
|
||||
html/dom/historical.html
|
||||
html/dom/partial-updates/tentative/template-for-innerHTML-outside.html
|
||||
html/dom/partial-updates/tentative/template-for-invalid.html
|
||||
|
||||
@@ -18,6 +18,43 @@ struct TreeReplacementPlan<'a> {
|
||||
}
|
||||
|
||||
impl DocumentRuntime {
|
||||
/// Replace every child of `parent` with the children of `fragment` while
|
||||
/// preserving the single DOM "replace all" mutation record.
|
||||
///
|
||||
/// `existing_children` must be captured before constructing `fragment`:
|
||||
/// ParentNode.replaceChildren() is allowed to move an existing child into
|
||||
/// that fragment before the final splice.
|
||||
pub(crate) fn replace_all_children_with_fragment_appending_to_current_reaction_queue(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host_ptr: *mut JsContextHost,
|
||||
parent: DomHandle,
|
||||
fragment: DomHandle,
|
||||
existing_children: &[DomHandle],
|
||||
) -> bool {
|
||||
let added_children = self.dom_host.child_handles(fragment).collect::<Vec<_>>();
|
||||
let records_enabled = self.dom_host.mutation_records_enabled();
|
||||
let removes_existing_children = !existing_children.is_empty();
|
||||
for child in existing_children {
|
||||
let _ = self
|
||||
.remove_child_appending_to_current_reaction_queue(scope, host_ptr, parent, *child);
|
||||
}
|
||||
let changed = self
|
||||
.append_child_appending_to_current_reaction_queue(scope, host_ptr, parent, fragment)
|
||||
|| removes_existing_children;
|
||||
if changed && records_enabled {
|
||||
crate::observer_runtime::coalesce_child_list_replacement_records(
|
||||
host_ptr,
|
||||
parent,
|
||||
&added_children,
|
||||
existing_children,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
pub(crate) fn replace_child_appending_to_current_reaction_queue(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
|
||||
@@ -170,8 +170,8 @@ pub(super) use content::{
|
||||
node_inner_html_setter_function, node_inner_text_getter_function,
|
||||
node_inner_text_setter_function, node_outer_html_getter_function,
|
||||
node_outer_html_setter_function, node_outer_text_getter_function,
|
||||
node_outer_text_setter_function, node_set_html_unsafe_callback, title_text_getter_function,
|
||||
title_text_setter_function,
|
||||
node_outer_text_setter_function, node_set_html_unsafe_callback,
|
||||
set_inner_text_in_reaction_scope, title_text_getter_function, title_text_setter_function,
|
||||
};
|
||||
pub(super) use dataset::{build_dom_string_map_wrapper_template, node_dataset_getter_function};
|
||||
use details_dialog::{
|
||||
@@ -2951,7 +2951,11 @@ fn script_source_setter_function<'s>(
|
||||
let _ = unsafe { &mut *runtime_ptr }
|
||||
.dom_host_mut()
|
||||
.set_script_text_internal_slot(handle, &text);
|
||||
let _ = set_text_content_in_reaction_scope(scope, runtime_ptr, handle, &text);
|
||||
if sink == TrustedScriptElementSink::InnerText {
|
||||
let _ = set_inner_text_in_reaction_scope(scope, runtime_ptr, handle, &text);
|
||||
} else {
|
||||
let _ = set_text_content_in_reaction_scope(scope, runtime_ptr, handle, &text);
|
||||
}
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
custom_elements,
|
||||
context_bootstrap, custom_elements,
|
||||
document_runtime::DomHandle,
|
||||
dom::native::{Element, Node},
|
||||
dom::native::{Element, Node, NodeType},
|
||||
style_engine::{ComputedDisplayKind, ComputedTextWrapModeKind, ComputedWhiteSpaceCollapseKind},
|
||||
util::v8_string,
|
||||
};
|
||||
@@ -1115,6 +1115,122 @@ pub(in crate::native_bridge) fn node_inner_text_getter_function<'s>(
|
||||
rv.set(value.into());
|
||||
}
|
||||
|
||||
fn rendered_text_setter_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
member: &'static str,
|
||||
) -> Option<String> {
|
||||
let options = crate::webidl::StringOptions {
|
||||
treat_null_as_empty_string: true,
|
||||
};
|
||||
match crate::webidl::convert_with_options::<crate::webidl::DomString>(
|
||||
scope,
|
||||
value,
|
||||
crate::webidl::Context::member("HTMLElement", member),
|
||||
&options,
|
||||
) {
|
||||
Ok(value) => Some(value.0),
|
||||
Err(error) => {
|
||||
crate::webidl::throw_error(scope, &error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn append_rendered_text_node(
|
||||
runtime: &mut JsContextHost,
|
||||
document: DomHandle,
|
||||
fragment: DomHandle,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
let text = runtime.create_text_node_for_document(document, value);
|
||||
runtime
|
||||
.dom_host_mut()
|
||||
.append_child_without_mutation_effects(fragment, text)
|
||||
}
|
||||
|
||||
fn append_rendered_break_node(
|
||||
runtime: &mut JsContextHost,
|
||||
document: DomHandle,
|
||||
fragment: DomHandle,
|
||||
) -> bool {
|
||||
let line_break = runtime.create_element("br");
|
||||
if runtime.dom_host().owner_document_handle(line_break) != Some(document)
|
||||
&& runtime
|
||||
.initialize_new_native_node_owner_document(document, line_break)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
runtime
|
||||
.dom_host_mut()
|
||||
.append_child_without_mutation_effects(fragment, line_break)
|
||||
}
|
||||
|
||||
fn rendered_text_fragment(
|
||||
runtime: &mut JsContextHost,
|
||||
document: DomHandle,
|
||||
value: &str,
|
||||
) -> Option<DomHandle> {
|
||||
let fragment = runtime.create_document_fragment_for_document(document);
|
||||
let mut text_start = 0;
|
||||
let mut characters = value.char_indices().peekable();
|
||||
while let Some((position, character)) = characters.next() {
|
||||
if !matches!(character, '\r' | '\n') {
|
||||
continue;
|
||||
}
|
||||
if text_start < position
|
||||
&& !append_rendered_text_node(runtime, document, fragment, &value[text_start..position])
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if character == '\r'
|
||||
&& characters
|
||||
.peek()
|
||||
.is_some_and(|(_, character)| *character == '\n')
|
||||
{
|
||||
let (line_feed_position, _) = characters.next()?;
|
||||
text_start = line_feed_position + '\n'.len_utf8();
|
||||
} else {
|
||||
text_start = position + character.len_utf8();
|
||||
}
|
||||
if !append_rendered_break_node(runtime, document, fragment) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if text_start < value.len()
|
||||
&& !append_rendered_text_node(runtime, document, fragment, &value[text_start..])
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(fragment)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn set_inner_text_in_reaction_scope(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
handle: DomHandle,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
custom_elements::with_custom_element_reaction_scope(scope, runtime_ptr, |scope| {
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let Some(document) = runtime.dom_host().owner_document_handle(handle) else {
|
||||
return false;
|
||||
};
|
||||
let existing_children = runtime.dom_host().child_handles(handle).collect::<Vec<_>>();
|
||||
let Some(fragment) = rendered_text_fragment(runtime, document, value) else {
|
||||
return false;
|
||||
};
|
||||
runtime.replace_all_children_with_fragment_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
fragment,
|
||||
&existing_children,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn node_inner_text_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1125,10 +1241,10 @@ pub(in crate::native_bridge) fn node_inner_text_setter_function<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(value) = property_string_value(scope, args.get(0)) else {
|
||||
let Some(value) = rendered_text_setter_value(scope, args.get(0), "innerText") else {
|
||||
return;
|
||||
};
|
||||
let _ = set_text_content_in_reaction_scope(scope, runtime_ptr, handle, &value);
|
||||
let _ = set_inner_text_in_reaction_scope(scope, runtime_ptr, handle, &value);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
@@ -1181,6 +1297,51 @@ pub(in crate::native_bridge) fn node_outer_text_getter_function<'s>(
|
||||
node_inner_text_getter_function(scope, args, rv);
|
||||
}
|
||||
|
||||
fn is_text_node(runtime: &JsContextHost, handle: DomHandle) -> bool {
|
||||
runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.is_some_and(|node| node.node_type() == NodeType::Text)
|
||||
}
|
||||
|
||||
fn merge_with_next_text_node(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime: &mut JsContextHost,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
handle: DomHandle,
|
||||
) {
|
||||
let Some(next) = runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(Node::next_sibling)
|
||||
.filter(|next| is_text_node(runtime, *next))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parent) = runtime.dom_host().node(handle).and_then(Node::parent_node) else {
|
||||
return;
|
||||
};
|
||||
let Some(mut value) = runtime.character_data_utf16_units(handle) else {
|
||||
return;
|
||||
};
|
||||
let Some(next_value) = runtime.character_data_utf16_units(next) else {
|
||||
return;
|
||||
};
|
||||
let insertion_offset = value.len() as u32;
|
||||
let inserted_count = next_value.len() as u32;
|
||||
value.extend_from_slice(&next_value);
|
||||
let _ = runtime.set_character_data_utf16_units_for_edit(scope, runtime_ptr, handle, &value);
|
||||
context_bootstrap::live_ranges_character_data_edit(
|
||||
scope,
|
||||
handle,
|
||||
insertion_offset,
|
||||
0,
|
||||
inserted_count,
|
||||
);
|
||||
let _ =
|
||||
runtime.remove_child_appending_to_current_reaction_queue(scope, runtime_ptr, parent, next);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn node_outer_text_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1191,14 +1352,11 @@ pub(in crate::native_bridge) fn node_outer_text_setter_function<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(value) = property_string_value(scope, args.get(0)) else {
|
||||
let Some(value) = rendered_text_setter_value(scope, args.get(0), "outerText") else {
|
||||
return;
|
||||
};
|
||||
let Some(parent) = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(Node::parent_node)
|
||||
else {
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let Some(parent) = runtime.dom_host().node(handle).and_then(Node::parent_node) else {
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NoModificationAllowedError",
|
||||
@@ -1207,28 +1365,42 @@ pub(in crate::native_bridge) fn node_outer_text_setter_function<'s>(
|
||||
);
|
||||
return;
|
||||
};
|
||||
let previous = runtime.dom_host().node(handle).and_then(Node::prev_sibling);
|
||||
let next = runtime.dom_host().node(handle).and_then(Node::next_sibling);
|
||||
custom_elements::with_custom_element_reaction_scope(scope, runtime_ptr, |scope| {
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
if value.is_empty() {
|
||||
let _ = runtime.remove_child_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
parent,
|
||||
handle,
|
||||
);
|
||||
let Some(document) = runtime.dom_host().owner_document_handle(handle) else {
|
||||
return;
|
||||
};
|
||||
let Some(fragment) = rendered_text_fragment(runtime, document, &value) else {
|
||||
return;
|
||||
};
|
||||
if runtime.dom_host().child_handles(fragment).next().is_none()
|
||||
&& !append_rendered_text_node(runtime, document, fragment, "")
|
||||
{
|
||||
return;
|
||||
}
|
||||
let text = match runtime.dom_host().owner_document_handle(parent) {
|
||||
Some(document_handle) => runtime.create_text_node_for_document(document_handle, &value),
|
||||
None => runtime.create_text_node(&value),
|
||||
};
|
||||
let _ = runtime.replace_child_appending_to_current_reaction_queue(
|
||||
if !runtime.replace_child_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
parent,
|
||||
text,
|
||||
fragment,
|
||||
handle,
|
||||
);
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if let Some(next) = next
|
||||
&& let Some(previous_to_next) = runtime
|
||||
.dom_host()
|
||||
.node(next)
|
||||
.and_then(Node::prev_sibling)
|
||||
.filter(|previous| is_text_node(runtime, *previous))
|
||||
{
|
||||
merge_with_next_text_node(scope, runtime, runtime_ptr, previous_to_next);
|
||||
}
|
||||
if let Some(previous) = previous.filter(|previous| is_text_node(runtime, *previous)) {
|
||||
merge_with_next_text_node(scope, runtime, runtime_ptr, previous);
|
||||
}
|
||||
});
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
@@ -229,39 +229,14 @@ pub(in crate::native_bridge) fn node_replace_children_callback<'s>(
|
||||
document_handle,
|
||||
&inserted,
|
||||
)?;
|
||||
let added_children = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.child_handles(fragment)
|
||||
.collect::<Vec<_>>();
|
||||
let records_enabled = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.mutation_records_enabled();
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let removes_existing_children = !existing_children.is_empty();
|
||||
for &child in &existing_children {
|
||||
let _ = runtime.remove_child_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
parent,
|
||||
child,
|
||||
);
|
||||
}
|
||||
let changed = runtime.append_child_appending_to_current_reaction_queue(
|
||||
runtime.replace_all_children_with_fragment_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
parent,
|
||||
fragment,
|
||||
) || removes_existing_children;
|
||||
if changed && records_enabled {
|
||||
crate::observer_runtime::coalesce_child_list_replacement_records(
|
||||
runtime_ptr,
|
||||
parent,
|
||||
&added_children,
|
||||
&existing_children,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
&existing_children,
|
||||
);
|
||||
Some(())
|
||||
})
|
||||
else {
|
||||
|
||||
+1
-1
@@ -1111,7 +1111,7 @@ fn custom_element_inner_and_outer_text_replacements_enqueue_disconnected_callbac
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
"0||0||inner:connected|inner:disconnected|outer:connected|outer:disconnected"
|
||||
"0||1||inner:connected|inner:disconnected|outer:connected|outer:disconnected"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -1008,7 +1008,9 @@ fn detached_inner_outer_text_use_html_element_prototype_accessors() {
|
||||
assert(inner.get.call(read) === "Alpha Beta", "innerText getter");
|
||||
assert(outer.get.call(replace) === "Old", "outerText getter");
|
||||
inner.set.call(read, "Line one\nLine two");
|
||||
assert(read.textContent === "Line one\nLine two", "innerText setter");
|
||||
assert(read.innerHTML === "Line one<br>Line two", "innerText setter fragment");
|
||||
assert(read.textContent === "Line oneLine two", "innerText setter text content");
|
||||
assert(read.querySelector("br").ownerDocument === doc, "innerText setter owner document");
|
||||
outer.set.call(replace, "Done");
|
||||
assert(doc.querySelector("#replace").textContent === "Done", "outerText setter");
|
||||
assert(!own(read, "innerText"), "innerText should not be own after set");
|
||||
|
||||
@@ -990,6 +990,81 @@ fn htmlelement_standard_accessors_live_on_owner_prototypes() {
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_and_outer_text_setters_build_rendered_fragments_and_merge_text_endpoints() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://rendered-text-setters.test/",
|
||||
"<!doctype html><html><head></head><body></body></html>",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
|
||||
const inner = document.createElement("div");
|
||||
const oldChild = inner.appendChild(document.createElement("span"));
|
||||
const observer = new MutationObserver(() => {});
|
||||
observer.observe(inner, { childList: true });
|
||||
inner.innerText = "alpha\r\nbeta\n\r";
|
||||
assert(inner.innerHTML === "alpha<br>beta<br><br>", "innerText rendered fragment");
|
||||
assert(inner.childNodes.length === 5, "innerText child count");
|
||||
assert(inner.firstChild !== oldChild, "innerText replaces the old child");
|
||||
assert(Array.from(inner.childNodes).every(node => node.ownerDocument === document), "innerText owner document");
|
||||
const records = observer.takeRecords();
|
||||
assert(records.length === 1, "innerText replacement mutation count");
|
||||
assert(records[0].addedNodes.length === 5, "innerText replacement added nodes");
|
||||
assert(records[0].removedNodes.length === 1 && records[0].removedNodes[0] === oldChild, "innerText replacement removed node");
|
||||
|
||||
inner.innerText = null;
|
||||
assert(inner.childNodes.length === 0, "innerText null is empty");
|
||||
inner.innerText = undefined;
|
||||
assert(inner.textContent === "undefined", "innerText undefined stringifies");
|
||||
|
||||
const detachedDocument = document.implementation.createHTMLDocument("");
|
||||
const detached = detachedDocument.createElement("div");
|
||||
detached.innerText = "left\nright";
|
||||
assert(detached.innerHTML === "left<br>right", "detached innerText rendered fragment");
|
||||
assert(detached.querySelector("br").ownerDocument === detachedDocument, "detached break owner document");
|
||||
|
||||
const parent = document.createElement("div");
|
||||
const first = parent.appendChild(document.createTextNode("A"));
|
||||
const previous = parent.appendChild(document.createTextNode("B"));
|
||||
const replaced = parent.appendChild(document.createElement("span"));
|
||||
const next = parent.appendChild(document.createTextNode("D"));
|
||||
const last = parent.appendChild(document.createTextNode("E"));
|
||||
replaced.outerText = "Replaced";
|
||||
assert(parent.childNodes.length === 3, "outerText only merges endpoints");
|
||||
assert(parent.childNodes[0] === first && parent.childNodes[2] === last, "outerText keeps distant text nodes separate");
|
||||
assert(parent.childNodes[1] === previous && previous.data === "BReplacedD", "outerText merges adjacent text nodes");
|
||||
assert(replaced.parentNode === null && next.parentNode === null, "outerText detaches replaced nodes");
|
||||
|
||||
const emptyParent = document.createElement("div");
|
||||
const emptyTarget = emptyParent.appendChild(document.createElement("span"));
|
||||
emptyTarget.outerText = null;
|
||||
assert(emptyParent.childNodes.length === 1, "outerText empty child count");
|
||||
assert(emptyParent.firstChild.nodeType === Node.TEXT_NODE && emptyParent.firstChild.data === "", "outerText keeps an empty Text node");
|
||||
|
||||
const breaksParent = document.createElement("div");
|
||||
const breaksTarget = breaksParent.appendChild(document.createElement("span"));
|
||||
breaksTarget.outerText = "\n\r\n\r";
|
||||
assert(breaksParent.innerHTML === "<br><br><br>", "outerText newline conversion");
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.innerText = "one\ntwo";
|
||||
assert(script.innerHTML === "one<br>two", "HTMLScriptElement innerText rendered fragment");
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("rendered text setter probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_state_and_collection_accessors_live_on_document_prototype() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
|
||||
Reference in New Issue
Block a user