mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Merge pull request #2543 from lightpanda-io/frame_document_write
Improve document.write within an iframe
This commit is contained in:
@@ -398,6 +398,16 @@ pub fn staticScriptsDone(self: *ScriptManagerBase) void {
|
||||
self.evaluate();
|
||||
}
|
||||
|
||||
// A script-created parser (document.open/write/close) finished. Run any
|
||||
// deferred scripts it produced. Unlike staticScriptsDone, this can run after
|
||||
// the initial parse already completed (so it must not re-assert the flag): a
|
||||
// frame that was loaded (or document.write'd into multiple times) keeps
|
||||
// static_scripts_done set, and evaluate() only drains defer_scripts when it is.
|
||||
pub fn scriptCreatedParseDone(self: *ScriptManagerBase) void {
|
||||
self.static_scripts_done = true;
|
||||
self.evaluate();
|
||||
}
|
||||
|
||||
pub fn evaluate(self: *ScriptManagerBase) void {
|
||||
if (self.is_evaluating) {
|
||||
// It's possible for a script.eval to cause evaluate to be called again.
|
||||
|
||||
50
src/browser/tests/frames/document_write.html
Normal file
50
src/browser/tests/frames/document_write.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<!--
|
||||
document.write into an iframe must parse and run the written markup in the
|
||||
IFRAME's own frame, not the calling (main) frame. Previously the scripts ran
|
||||
in the caller's frame, so their globals / window / parent were the main
|
||||
page's — which broke cross-frame messaging (parent resolved to self).
|
||||
-->
|
||||
<script id=iframe_write_runs_in_iframe>
|
||||
{
|
||||
const iframe = document.createElement('iframe');
|
||||
document.documentElement.appendChild(iframe);
|
||||
|
||||
const doc = iframe.contentDocument;
|
||||
doc.write('<!DOCTYPE html><html><body><script>window.ranHere = true; window.parentIsSelf = (window.parent === window);<\/script></body></html>');
|
||||
doc.close();
|
||||
|
||||
// Read the flags from the IFRAME's window. If the script had (wrongly) run in
|
||||
// the main frame, they'd be set there and these would read undefined.
|
||||
testing.expectEqual(true, iframe.contentWindow.ranHere);
|
||||
// The script's `parent` is the main window, so parent !== self.
|
||||
testing.expectEqual(false, iframe.contentWindow.parentIsSelf);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
A deferred (module) script written into an iframe must still run. A freshly
|
||||
loaded iframe never fires its static-parse "done" signal, so document.close()
|
||||
is what flushes the deferred scripts the write produced.
|
||||
-->
|
||||
<script id=iframe_write_flushes_deferred_module type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
document.documentElement.appendChild(iframe);
|
||||
|
||||
const doc = iframe.contentDocument;
|
||||
doc.write('<!DOCTYPE html><html><body><script type="module">window.moduleRan = true;<\/script></body></html>');
|
||||
doc.close();
|
||||
|
||||
// Give the deferred module a tick to run, then confirm it executed in the
|
||||
// iframe (not the main frame, where contentWindow.moduleRan would be unset).
|
||||
setTimeout(() => state.resolve(), 5);
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, iframe.contentWindow.moduleRan);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -718,7 +718,13 @@ pub fn writeln(self: *Document, text: []const []const u8, frame: *Frame) !void {
|
||||
return self.writeInternal(text, true, frame);
|
||||
}
|
||||
|
||||
fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool, frame: *Frame) !void {
|
||||
fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool, call_frame: *Frame) !void {
|
||||
// document.write acts on this document's own frame, which isn't necessarily
|
||||
// the calling frame — e.g. a parent frame writing into an iframe's document.
|
||||
// The markup (and any scripts it contains) must be parsed and run in that
|
||||
// document's context, not the caller's.
|
||||
const frame = self._frame orelse call_frame;
|
||||
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -730,10 +736,13 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
const html = blk: {
|
||||
var joined: std.ArrayList(u8) = .empty;
|
||||
for (text) |str| {
|
||||
try joined.appendSlice(frame.call_arena, str);
|
||||
// Scratch buffer, consumed synchronously below. Keep it on the
|
||||
// active (calling) frame's call_arena: a script run by the parse
|
||||
// could reset the document frame's call_arena underfoot.
|
||||
try joined.appendSlice(call_frame.call_arena, str);
|
||||
}
|
||||
if (append_newline) {
|
||||
try joined.append(frame.call_arena, '\n');
|
||||
try joined.append(call_frame.call_arena, '\n');
|
||||
}
|
||||
break :blk joined.items;
|
||||
};
|
||||
@@ -827,7 +836,9 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
self._write_insertion_point = children_to_insert.getLast();
|
||||
}
|
||||
|
||||
pub fn open(self: *Document, frame: *Frame) !*Document {
|
||||
pub fn open(self: *Document, call_frame: *Frame) !*Document {
|
||||
const frame = self._frame orelse call_frame;
|
||||
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -869,7 +880,9 @@ pub fn open(self: *Document, frame: *Frame) !*Document {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn close(self: *Document, frame: *Frame) !void {
|
||||
pub fn close(self: *Document, call_frame: *Frame) !void {
|
||||
const frame = self._frame orelse call_frame;
|
||||
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -889,6 +902,12 @@ pub fn close(self: *Document, frame: *Frame) !void {
|
||||
defer self._script_created_parser = null;
|
||||
try self._script_created_parser.?.done();
|
||||
|
||||
// The write'd markup is fully parsed; run any deferred scripts it produced
|
||||
// (e.g. inline modules) before firing the load event. This frame's initial
|
||||
// parse may never have set static_scripts_done (e.g. a freshly-loaded
|
||||
// iframe written into via document.write), so we can't rely on it.
|
||||
frame._script_manager.base.scriptCreatedParseDone();
|
||||
|
||||
frame.documentIsComplete();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user