mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 02:36:06 -04:00
Merge pull request #2824 from lightpanda-io/script_innerHTML
fix: Fix innerHTML parsing based on the target
This commit is contained in:
@@ -320,15 +320,22 @@ fn shouldStripElement(el: *Node.Element, opts: Opts, frame: *Frame) bool {
|
||||
}
|
||||
|
||||
fn shouldEscapeText(node_: ?*Node) bool {
|
||||
// Raw text elements serialize their text content literally rather than
|
||||
// HTML-escaping it
|
||||
const node = node_ orelse return true;
|
||||
if (node.is(Node.Element.Html.Script) != null) {
|
||||
return false;
|
||||
}
|
||||
// When scripting is enabled, <noscript> is a raw text element per the HTML spec
|
||||
// (https://html.spec.whatwg.org/multipage/parsing.html#serialising-html-fragments).
|
||||
// Its text content must not be HTML-escaped during serialization.
|
||||
if (node.is(Node.Element.Html.Generic)) |generic| {
|
||||
if (generic._tag == .noscript) return false;
|
||||
const element = node.is(Node.Element) orelse return true;
|
||||
const html_element = node.is(Node.Element.Html) orelse return true;
|
||||
|
||||
switch (html_element._type) {
|
||||
.style, .script, .iframe => return false,
|
||||
else => {
|
||||
const tag = element.getTagNameLower();
|
||||
inline for (.{ "xmp", "noembed", "noframes", "plaintext", "noscript" }) |raw_text_tag| {
|
||||
if (std.mem.eql(u8, tag, raw_text_tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -259,9 +259,16 @@ pub fn parseXML(self: *Parser, xml: []const u8) void {
|
||||
}
|
||||
|
||||
pub fn parseFragment(self: *Parser, html: []const u8) void {
|
||||
const context_name: []const u8 = if (self.container.node.is(Element)) |el|
|
||||
el.getLocalName()
|
||||
else
|
||||
"";
|
||||
|
||||
h5e.html5ever_parse_fragment(
|
||||
html.ptr,
|
||||
html.len,
|
||||
context_name.ptr,
|
||||
context_name.len,
|
||||
&self.container,
|
||||
self,
|
||||
createElementCallback,
|
||||
|
||||
@@ -70,6 +70,8 @@ pub extern "c" fn html5ever_parse_document_with_encoding(
|
||||
pub extern "c" fn html5ever_parse_fragment(
|
||||
html: [*c]const u8,
|
||||
len: usize,
|
||||
context_name: [*c]const u8,
|
||||
context_name_len: usize,
|
||||
doc: *anyopaque,
|
||||
ctx: *anyopaque,
|
||||
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
|
||||
|
||||
@@ -201,3 +201,42 @@
|
||||
d1.innerHTML = '<span style="visibility:hidden">X</span>Y';
|
||||
testing.expectEqual("XY", d1.innerText);
|
||||
</script>
|
||||
|
||||
<script id=rawTextContext>
|
||||
// Setting innerHTML on a raw-text element (script/style/textarea/title) must
|
||||
// parse the content in the matching raw-text tokenizer state. In particular a
|
||||
// '<' followed by a letter is plain text, NOT the start of a tag — otherwise
|
||||
// the body is truncated at the first '<' (e.g. `for(;l<localStorage.length;)`
|
||||
// would be swallowed, leaving a half-statement that fails to compile).
|
||||
const script = document.createElement('script');
|
||||
const body = 'window.__x=0; for(let l=0;l<localStorage.length+3;){window.__x=l;l++;}';
|
||||
script.innerHTML = body;
|
||||
testing.expectEqual(body, script.innerHTML);
|
||||
testing.expectEqual(1, script.childNodes.length);
|
||||
testing.expectEqual(body, script.textContent);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = 'a<b{color:red}';
|
||||
testing.expectEqual('a<b{color:red}', style.innerHTML);
|
||||
testing.expectEqual(1, style.childNodes.length);
|
||||
|
||||
// textarea / title are RCDATA: '<' + letter stays text (not a tag) and
|
||||
// character references are decoded on parse. On serialization the content is
|
||||
// re-escaped (RCDATA elements are NOT raw-text-serialized), so the round-trip
|
||||
// shows '<' as < and the decoded '&' re-escaped as &.
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.innerHTML = 'x < y & z';
|
||||
testing.expectEqual('x < y & z', textarea.value);
|
||||
testing.expectEqual('x < y & z', textarea.innerHTML);
|
||||
|
||||
const title = document.createElement('title');
|
||||
title.innerHTML = '1 < 2 < three';
|
||||
testing.expectEqual(1, title.childNodes.length);
|
||||
testing.expectEqual('1 < 2 < three', title.textContent);
|
||||
testing.expectEqual('1 < 2 < three', title.innerHTML);
|
||||
|
||||
// A normal element stays in the data state: '<b>' is a real child element.
|
||||
d1.innerHTML = 'a<b>bold</b>';
|
||||
testing.expectEqual('a<b>bold</b>', d1.innerHTML);
|
||||
testing.expectEqual('B', d1.lastChild.tagName);
|
||||
</script>
|
||||
|
||||
@@ -461,6 +461,8 @@ pub extern "C" fn encoding_max_encode_buffer_length(
|
||||
pub extern "C" fn html5ever_parse_fragment(
|
||||
html: *mut c_uchar,
|
||||
len: usize,
|
||||
context_name: *const c_uchar,
|
||||
context_name_len: usize,
|
||||
document: Ref,
|
||||
ctx: Ref,
|
||||
create_element_callback: CreateElementCallback,
|
||||
@@ -510,10 +512,24 @@ pub extern "C" fn html5ever_parse_fragment(
|
||||
};
|
||||
|
||||
let bytes = unsafe { std::slice::from_raw_parts(html, len) };
|
||||
|
||||
// The initial state of the parser is dertmined by the context element.
|
||||
// E.g., parsing the content of a script tag behaves differently than
|
||||
// parsing the content of the body.
|
||||
let context_local = if context_name.is_null() || context_name_len == 0 {
|
||||
LocalName::from("body")
|
||||
} else {
|
||||
let name_bytes = unsafe { std::slice::from_raw_parts(context_name, context_name_len) };
|
||||
match std::str::from_utf8(name_bytes) {
|
||||
Ok(name) => LocalName::from(name),
|
||||
Err(_) => LocalName::from("body"),
|
||||
}
|
||||
};
|
||||
|
||||
parse_fragment(
|
||||
sink,
|
||||
Default::default(),
|
||||
QualName::new(None, ns!(html), LocalName::from("body")),
|
||||
QualName::new(None, ns!(html), context_local),
|
||||
vec![], // attributes
|
||||
false, // context_element_allows_scripting
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user