Skip to content
Permalink
Browse files
Adding iframe flushes microtasks synchronously with dirty stack
https://bugs.webkit.org/show_bug.cgi?id=235322

Reviewed by Chris Dumez.

The bug was caused by Document::finishedParsing synchronously performing a microtask checkpoint.
When this function also gets called during the initialization of iframe newly connected to a document,
it erroneously executes microtasks associated with the current event loop.

This is likely a regression from http://commits.webkit.org/r234944 where we introduced this workaround
for firing DOMContentLoaded synchronously. Avoid calling this function in this specific circumstances.

Added a new test for various types of iframe insertion.
Note that load event always fires synchronously when inserting an iframe is loading about:blank.
This behavior matches Blink whereas Gecko fires load event fully asynchronously.

* Source/WebCore/dom/Document.cpp:
(WebCore::Document::finishedParsing): Fixed the bug by skipping the aforementioned workaround when
we're in the middle of initializing an iframe.
* Source/WebCore/loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::maybeLoadEmpty): Added. Set m_isInFinishedLoadingOfEmptyDocument to true
while calling DocumentLoader::finishedParsing.
* Source/WebCore/loader/DocumentLoader.h:
(WebCore::DocumentLoader::isInFinishedLoadingOfEmptyDocument const): Added.

* LayoutTests/http/tests/eventloop/inserting-iframe-should-not-perform-microtask-checkpoint-expected.txt: Added.
* LayoutTests/http/tests/eventloop/inserting-iframe-should-not-perform-microtask-checkpoint.html: Added.

Canonical link: https://commits.webkit.org/252015@main
  • Loading branch information
rniwa committed Jun 30, 2022
1 parent e0aac4e commit ea74655e102a2a6e63a9c71a38873ddb03f5bf33
Showing 6 changed files with 109 additions and 1 deletion.
@@ -0,0 +1,34 @@
This tests inserting an empty iframe after scheduling a microtask.
WebKit should not perform a microtask checkpoint.


empty
begin inserting iframe
load event
end inserting iframe
microtask

about:blank
begin inserting iframe
load event
end inserting iframe
microtask

hello.html
begin inserting iframe
end inserting iframe
microtask
load event

data URL
begin inserting iframe
end inserting iframe
microtask
load event

srcdoc
begin inserting iframe
end inserting iframe
microtask
load event

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<body>
<p>This tests inserting an empty iframe after scheduling a microtask.<br>
WebKit should not perform a microtask checkpoint.</p>
<pre id="log"></pre>
<script>
let logCount = 0;

if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
}

const testCases = [
{name: 'empty', test: (iframe) => {}},
{name: 'about:blank', test: (iframe) => iframe.src = 'about:blank'},
{name: 'hello.html', test: (iframe) => iframe.src = 'resources/hello.html'},
{name: 'data URL', test: (iframe) => iframe.src = 'data:text/html,<!DOCTYPE html>data'},
{name: 'srcdoc', test: (iframe) => iframe.srcdoc = '<!DOCTYPE html>srcdoc'},
]

function test(name, testFunction) {
function log(message) {
document.getElementById('log').textContent += `${message}\n`;
}

return new Promise((resolve) => {
document.getElementById('log').textContent += `\n${name}\n`;

queueMicrotask(() => {
log('microtask');
});

const iframe = document.createElement("iframe");
iframe.onload = () => {
log('load event');
setTimeout(nextTest);
}
testFunction(iframe);
log('begin inserting iframe');
document.body.appendChild(iframe);
log('end inserting iframe');
});
}

onload = nextTest;

let testIndex = 0;
function nextTest() {
if (testIndex >= testCases.length) {
if (window.testRunner)
testRunner.notifyDone();
return;
}
test(testCases[testIndex].name, testCases[testIndex].test);
testIndex++;
}

</script>
</body>
</html>
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<p>hello</p>
</body>
</html>
@@ -6252,7 +6252,10 @@ void Document::finishedParsing()
}

// FIXME: Schedule a task to fire DOMContentLoaded event instead. See webkit.org/b/82931
eventLoop().performMicrotaskCheckpoint();
auto* documentLoader = loader();
bool isInMiddleOfInitializingIframe = documentLoader && documentLoader->isInFinishedLoadingOfEmptyDocument();
if (!isInMiddleOfInitializingIframe)
eventLoop().performMicrotaskCheckpoint();
dispatchEvent(Event::create(eventNames().DOMContentLoadedEvent, Event::CanBubble::Yes, Event::IsCancelable::No));

if (!m_eventTiming.domContentLoadedEventEnd) {
@@ -2034,6 +2034,7 @@ bool DocumentLoader::maybeLoadEmpty()
}
}

SetForScope isInFinishedLoadingOfEmptyDocument { m_isInFinishedLoadingOfEmptyDocument, true };
finishedLoading();
return true;
}
@@ -459,6 +459,7 @@ class DocumentLoader
bool isContinuingLoadAfterProvisionalLoadStarted() const { return m_isContinuingLoadAfterProvisionalLoadStarted; }
void setIsContinuingLoadAfterProvisionalLoadStarted(bool isContinuingLoadAfterProvisionalLoadStarted) { m_isContinuingLoadAfterProvisionalLoadStarted = isContinuingLoadAfterProvisionalLoadStarted; }

bool isInFinishedLoadingOfEmptyDocument() const { return m_isInFinishedLoadingOfEmptyDocument; }
#if ENABLE(CONTENT_FILTERING)
bool contentFilterWillHandleProvisionalLoadFailure(const ResourceError&);
void contentFilterHandleProvisionalLoadFailure(const ResourceError&);
@@ -604,6 +605,7 @@ class DocumentLoader
bool m_isClientRedirect { false };
bool m_isLoadingMultipartContent { false };
bool m_isContinuingLoadAfterProvisionalLoadStarted { false };
bool m_isInFinishedLoadingOfEmptyDocument { false };

// FIXME: Document::m_processingLoadEvent and DocumentLoader::m_wasOnloadDispatched are roughly the same
// and should be merged.

0 comments on commit ea74655

Please sign in to comment.