diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 42b3fa988..ff9cb5e0f 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2611,7 +2611,6 @@ domparsing/DOMParser-parseFromString-xml-internal-subset.html domparsing/DOMParser-parseFromString-xml-parsererror.html domparsing/DOMParser-parseFromString-xml.html domparsing/XMLSerializer-serializeToString.html -domparsing/insert_adjacent_html.html domparsing/tentative/all-stream-methods-with-trusted-types-no-policy.html domparsing/tentative/positional-methods-with-trusted-types.html domparsing/tentative/positional-methods.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index af7942745..f70790314 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -3747,6 +3747,7 @@ domparsing/innerhtml-06.html domparsing/innerhtml-07.html domparsing/innerhtml-li-autoclosing.html domparsing/insert-adjacent.html +domparsing/insert_adjacent_html.html domparsing/outerhtml-01.html domparsing/outerhtml-02.html domparsing/style_attribute_html.html diff --git a/moli-renderer-v8/src/document_runtime/document_write.rs b/moli-renderer-v8/src/document_runtime/document_write.rs index bf6068d6c..90882ae9c 100644 --- a/moli-renderer-v8/src/document_runtime/document_write.rs +++ b/moli-renderer-v8/src/document_runtime/document_write.rs @@ -36,6 +36,7 @@ enum DocumentWriteParserPumpInput<'a> { enum HtmlFragmentParserContextMode { Standard, RangeCreateContextualFragment, + SiblingInsertion, } struct DocumentWriteParserMutationOwner<'a, 'scope, 'pin> { @@ -553,8 +554,11 @@ impl DocumentRuntime { .and_then(Node::local_name) .unwrap_or("body") .to_owned(); - if context_mode == HtmlFragmentParserContextMode::RangeCreateContextualFragment - && context_namespace == "http://www.w3.org/1999/xhtml" + if matches!( + context_mode, + HtmlFragmentParserContextMode::RangeCreateContextualFragment + | HtmlFragmentParserContextMode::SiblingInsertion + ) && context_namespace == "http://www.w3.org/1999/xhtml" && context_local_name.eq_ignore_ascii_case("html") { context_local_name = "body".to_owned(); @@ -946,7 +950,13 @@ impl DocumentRuntime { html: &str, insert: impl FnOnce(&mut Self, &mut v8::PinScope<'_, '_>, *mut JsContextHost, DomHandle) -> bool, ) -> bool { - let Some(fragment) = self.build_fragment_from_html( + let scripting_enabled = unsafe { &*host_ptr }.document_scripting_enabled(document_handle); + let context_mode = if context_handle == target { + HtmlFragmentParserContextMode::Standard + } else { + HtmlFragmentParserContextMode::SiblingInsertion + }; + let Some(fragment) = self.build_fragment_from_html_with_context_mode( scope, host_ptr, document_handle, @@ -954,11 +964,13 @@ impl DocumentRuntime { html, true, HtmlFragmentCustomElementUpgradeTiming::AfterInsertion, + context_mode, + scripting_enabled, + false, ) else { return false; }; let added_children = self.dom_host().child_handles(fragment).collect::>(); - let _ = target; let changed = insert(self, scope, host_ptr, fragment); if changed && !self.upgrade_inserted_html_fragment_custom_elements( diff --git a/moli-renderer-v8/src/native_bridge/element/tree_mutation/callbacks.rs b/moli-renderer-v8/src/native_bridge/element/tree_mutation/callbacks.rs index 6d095a1ec..98ad656ec 100644 --- a/moli-renderer-v8/src/native_bridge/element/tree_mutation/callbacks.rs +++ b/moli-renderer-v8/src/native_bridge/element/tree_mutation/callbacks.rs @@ -296,15 +296,26 @@ pub(in crate::native_bridge) fn node_insert_adjacent_html_callback<'s>( position, InsertAdjacentPosition::BeforeBegin | InsertAdjacentPosition::AfterEnd ); - if needs_parent - && unsafe { &*runtime_ptr } + if needs_parent { + let parent = unsafe { &*runtime_ptr } .dom_host() .node(target) - .and_then(Node::parent_node) - .is_none() - { - rv.set_undefined(); - return; + .and_then(Node::parent_node); + let has_no_modifiable_parent = parent.is_none_or(|parent| { + unsafe { &*runtime_ptr } + .dom_host() + .node(parent) + .is_none_or(Node::is_document) + }); + if has_no_modifiable_parent { + throw_dom_exception( + scope, + "NoModificationAllowedError", + 7, + "The element has no modifiable parent for sibling insertion.", + ); + return; + } } let Some(document_handle) = insert_adjacent_document_handle(unsafe { &*runtime_ptr }, target, position) diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom.rs index 684174feb..e9da7c364 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom.rs @@ -423,6 +423,82 @@ fn element_insert_adjacent_methods_parse_webidl_arguments() { "true|elementtextundefinedbold|bold|throw:TypeError|throw:TypeError|throw:RangeError|throw:TypeError|throw:SyntaxError|throw:TypeError|throw:TypeError|throw:TypeError|throw:TypeError" ); } + +#[test] +fn insert_adjacent_html_enforces_sibling_context_rules() { + let mut vm = new_storage_test_vm("https://insert-adjacent-sibling-context.test/"); + + let result = vm + .eval( + r#" +(() => { + const probe = callback => { + try { + callback(); + return 'missing'; + } catch (error) { + return `${error.name}:${error.code}`; + } + }; + const sources = ['', 'text', '', '
']; + const positions = ['beforebegin', 'afterend']; + const detached = document.createElement('div'); + const root = document.documentElement || + document.appendChild(document.createElement('html')); + const detachedErrors = positions.flatMap(position => + sources.map(source => probe(() => detached.insertAdjacentHTML(position, source))) + ); + const documentErrors = positions.flatMap(position => + sources.map(source => probe(() => root.insertAdjacentHTML(position, source))) + ); + + while (root.firstChild) { + root.removeChild(root.firstChild); + } + root.insertAdjacentHTML( + 'afterbegin', + '' + ); + const preservedInnerHtmlContext = + document.head?.id === 'inside-head' && + document.body?.id === 'inside-body' && + root.firstChild === document.head && + root.lastChild === document.body; + const head = document.head || + root.insertBefore(document.createElement('head'), root.firstChild); + const body = document.body || root.appendChild(document.createElement('body')); + head.insertAdjacentHTML('beforebegin', '

'); + body.insertAdjacentHTML('afterend', '

'); + const beforeHead = document.getElementById('before-head'); + const afterBody = document.getElementById('after-body'); + + return JSON.stringify({ + detachedErrors, + documentErrors, + preservedInnerHtmlContext, + counts: [ + document.getElementsByTagName('html').length, + document.getElementsByTagName('head').length, + document.getElementsByTagName('body').length + ], + placement: [ + beforeHead.nextSibling === head, + body.nextSibling === afterBody, + beforeHead.parentNode === root, + afterBody.parentNode === root + ] + }); +})() +"#, + ) + .expect("insertAdjacentHTML sibling context rules should evaluate"); + + assert_eq!( + result, + r#"{"detachedErrors":["NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7"],"documentErrors":["NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7","NoModificationAllowedError:7"],"preservedInnerHtmlContext":true,"counts":[1,1,1],"placement":[true,true,true,true]}"# + ); +} + #[test] fn document_fragment_and_shadow_root_get_element_by_id_match_browser_lookup_boundaries() { let mut vm = new_storage_test_vm("https://fragment-shadow-get-by-id.test/");