Skip to content

Commit

Permalink
Document render-blocking with <link rel=expect>
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=268743
<rdar://122797243>

Reviewed by Tim Nguyen.

Implements the render-blocking concept on Document, and the rel=expect attribute for <link>.
https://html.spec.whatwg.org/multipage/dom.html#block-rendering

utils.js changed to generate a unique URL for each parser delay, otherwise we
cache the loads (which may not be spec compliant, but unrelated to
render-blocking).

Render blocking uses the existing code addVisualUpdatePreventedReason to prevent rendering, and rAF.

Some of the test fail because they have no render blocking (since an attempt was
rejected due to a mismatched media query or similar). These tests expect
rendering to happen mid-parsing, but don't due to our existing layer tree
freezing code. I've opted not to change the existing behaviour for now, and only
have this kick in when there is explicit (and active) render blocking elements.

As an optimization, when an <a> element changes, we pass that element into the 'process internal
resource links' algorithm. Rather than doing a tree search for each link looking for matching anchors,
we can just directly check if the link matches the mutated <a>.

* LayoutTests/TestExpectations:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-002-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-004-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-005-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-007-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-009-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-010-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-013-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-014-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-015-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-016-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-017-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-018-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-019-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-020-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-021-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-022-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-023-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-025-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-026-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-028-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-029-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-030-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-031-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-032-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-034-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-035-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-036-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-037-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-038-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/remove-element-unblocks-rendering.optional-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/dom/render-blocking/support/utils.js:
(generateParserDelay):
* LayoutTests/imported/w3c/web-platform-tests/loading/resources/dummy.js: Added.
* LayoutTests/platform/glib/TestExpectations:
* Source/WebCore/dom/Document.cpp:
(WebCore::Document::setReadyState):
(WebCore::Document::setVisualUpdatesAllowed):
(WebCore::Document::addVisualUpdatePreventedReason):
(WebCore::Document::removeVisualUpdatePreventedReason):
(WebCore::Document::visualUpdatesSuppressionTimerFired):
(WebCore::Document::setVisualUpdatesAllowedByClient):
(WebCore::Document::suspend):
(WebCore::Document::resume):
(WebCore::Document::allowsAddingRenderBlockedElements const):
(WebCore::Document::isRenderBlocked const):
(WebCore::Document::blockRenderingOn):
(WebCore::Document::unblockRenderingOn):
(WebCore::Document::processInternalResourceLinks):
* Source/WebCore/dom/Document.h:
(WebCore::Document::visualUpdatesAllowed const):
* Source/WebCore/html/HTMLAnchorElement.cpp:
(WebCore::HTMLAnchorElement::attributeChanged):
(WebCore::HTMLAnchorElement::insertedIntoAncestor):
* Source/WebCore/html/HTMLAnchorElement.h:
* Source/WebCore/html/HTMLAttributeNames.in:
* Source/WebCore/html/HTMLLinkElement.cpp:
(WebCore::ExpectIdTargetObserver::ExpectIdTargetObserver):
(WebCore::ExpectIdTargetObserver::idTargetChanged):
(WebCore::HTMLLinkElement::attributeChanged):
(WebCore::HTMLLinkElement::process):
(WebCore::HTMLLinkElement::processInternalResourceLink):
(WebCore::HTMLLinkElement::unblockRendering):
(WebCore::HTMLLinkElement::removedFromAncestor):
(WebCore::HTMLLinkElement::blocking):
* Source/WebCore/html/HTMLLinkElement.h:
* Source/WebCore/html/HTMLLinkElement.idl:
* Source/WebCore/html/LinkRelAttribute.cpp:
(WebCore::LinkRelAttribute::LinkRelAttribute):
* Source/WebCore/html/LinkRelAttribute.h:

Canonical link: https://commits.webkit.org/282279@main
  • Loading branch information
mattwoodrow committed Aug 15, 2024
1 parent dc2829c commit 0e9bbd9
Show file tree
Hide file tree
Showing 45 changed files with 216 additions and 35 deletions.
11 changes: 11 additions & 0 deletions LayoutTests/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -6904,6 +6904,17 @@ imported/w3c/web-platform-tests/webcodecs/videoFrame-createImageBitmap.https.any
imported/w3c/web-platform-tests/webcodecs/video-encoder.https.any.worker.html [ DumpJSConsoleLogInStdErr ]
imported/w3c/web-platform-tests/webcodecs/video-encoder.https.any.html [ DumpJSConsoleLogInStdErr ]

# These tests don't actually have render blocking (due to adding the blocking too late, or non-matching
# media queries etc). They expect the test to render midway through parsing, but our existing layer tree
# freezing prevents that. These tests fail intentionally, unless we decide to change our existing heuristics.
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-007.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-014.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-017.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-018.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-021.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-022.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-036.html [ Failure ]

# AudioDecoder, AudioEncoder and AudioData implementations missing.
imported/w3c/web-platform-tests/webcodecs/audio-decoder.crossOriginIsolated.https.any.html [ Failure ]
imported/w3c/web-platform-tests/webcodecs/audio-decoder.crossOriginIsolated.https.any.worker.html [ Failure ]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

FAIL Supported tokens of the 'blocking' IDL attribute of the link element undefined is not an object (evaluating 'link.blocking.supports')
FAIL Setting the 'blocking' IDL attribute of the link element assert_equals: expected (string) "asdf" but got (undefined) undefined
PASS Supported tokens of the 'blocking' IDL attribute of the link element
PASS Setting the 'blocking' IDL attribute of the link element
FAIL Supported tokens of the 'blocking' IDL attribute of the script element undefined is not an object (evaluating 'script.blocking.supports')
FAIL Setting the 'blocking' IDL attribute of the script element assert_equals: expected (string) "asdf" but got (undefined) undefined
FAIL Supported tokens of the 'blocking' IDL attribute of the style element undefined is not an object (evaluating 'style.blocking.supports')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL blocking defers until needed element is parsed assert_false: expected false got true
PASS blocking defers until needed element is parsed

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing link in the head makes it no longer blocking assert_false: expected false got true
PASS removing link in the head makes it no longer blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing 'blocking' makes it no longer blocking assert_false: expected false got true
PASS removing 'blocking' makes it no longer blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL link with non-matching media has no effect assert_false: expected false got true
PASS link with non-matching media has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL changing media to non-matching makes it non blocking assert_false: expected false got true
PASS changing media to non-matching makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CONSOLE MESSAGE: Did not parse stylesheet at 'http://localhost:8800/html/dom/render-blocking/element-render-blocking-010.html' because non CSS MIME types are not allowed in strict mode.

FAIL changing rel to non-expect makes it non blocking assert_false: expected false got true
PASS changing rel to non-expect makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing href makes it no longer blocking assert_false: expected false got true
PASS removing href makes it no longer blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL link in the body has no effect assert_false: expected false got true
PASS link in the body has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing link the body makes it non blocking assert_false: expected false got true
PASS removing link the body makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing 'blocking' in the body makes it non blocking assert_false: expected false got true
PASS removing 'blocking' in the body makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL adding 'blocking=render' in the body has no effect assert_false: expected false got true
PASS adding 'blocking=render' in the body has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL changing media to matching in the body has no effect assert_false: expected false got true
PASS changing media to matching in the body has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL changing media to non-matching in the body makes it non blocking assert_false: expected false got true
PASS changing media to non-matching in the body makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CONSOLE MESSAGE: Did not parse stylesheet at 'http://localhost:8800/html/dom/render-blocking/element-render-blocking-020.html' because non CSS MIME types are not allowed in strict mode.

FAIL changing rel to non-expect in the body makes it non blocking assert_false: expected false got true
PASS changing rel to non-expect in the body makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CONSOLE MESSAGE: Did not parse stylesheet at 'http://localhost:8800/html/dom/render-blocking/element-render-blocking-021.html' because non CSS MIME types are not allowed in strict mode.

FAIL changing rel to expect in the body has no effect assert_false: expected false got true
PASS changing rel to expect in the body has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL adding href in the body has no effect assert_false: expected false got true
PASS adding href in the body has no effect

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing href in the body makes it non blocking assert_false: expected false got true
PASS removing href in the body makes it non blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL adding an id in the body satisfies render block assert_false: expected false got true
PASS adding an id in the body satisfies render block

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing id after it was renderer keeps render block satisfied assert_false: expected false got true
PASS removing id after it was renderer keeps render block satisfied

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL removing some links but not all keeps at least the matching link blocking assert_false: expected false got true
PASS removing some links but not all keeps at least the matching link blocking

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL blocking defers frames until full parsing assert_false: expected false got true
PASS blocking defers frames until full parsing

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL blocking defers frames until full parsing assert_false: expected false got true
PASS blocking defers frames until full parsing

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL blocking defers frames until full parsing assert_false: expected false got true
PASS blocking defers frames until full parsing

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL blocking defers frames until full parsing assert_false: expected false got true
PASS blocking defers frames until full parsing

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL relative URLs that match this document are OK assert_false: expected false got true
PASS relative URLs that match this document are OK

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL relative URLs that match this document are OK, regarless of <base> assert_false: expected false got true
PASS relative URLs that match this document are OK, regarless of <base>

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL link URLs are relative to base URL, not to document URL assert_false: expected false got true
PASS link URLs are relative to base URL, not to document URL

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL relative URLs that match this document are OK, regarless of <base> assert_false: expected false got true
PASS relative URLs that match this document are OK, regarless of <base>

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

FAIL link rel=expect: only connected elements are eligible assert_false: The last element should not be there yet, even though it's created (in a shadow root) expected false got true
PASS link rel=expect: only connected elements are eligible

Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ PASS Render-blocking on parser-inserted async module script is cancellable
PASS Render-blocking on script-inserted stylesheet link is cancellable
PASS Render-blocking on script-inserted script is cancellable
PASS Render-blocking on script-inserted module script is cancellable
PASS Render-blocking on script-inserted inline style is cancellable
FAIL Render-blocking on script-inserted inline style is cancellable assert_false: expected false got true

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
function generateParserDelay(seconds = 1) {
seconds += (Math.random() / 10.0);
document.write(`
<script src="/loading/resources/dummy.js?pipe=trickle(d${seconds})"></script>
`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* Nothing to see here */
2 changes: 2 additions & 0 deletions LayoutTests/platform/glib/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -4121,6 +4121,8 @@ imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-011.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-020.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-021.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-024.html [ Failure ]
imported/w3c/web-platform-tests/html/dom/render-blocking/element-render-blocking-027.html [ Failure ]

imported/w3c/web-platform-tests/html/browsers/browsing-the-web/read-media/pageload-video.html [ Failure ]

Expand Down
37 changes: 37 additions & 0 deletions Source/WebCore/dom/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,7 @@ void Document::removeVisualUpdatePreventedReasons(OptionSet<VisualUpdatesPrevent

if (RefPtr frame = this->frame())
frame->checkedLoader()->completePageTransitionIfNeeded();
scheduleRenderingUpdate({ });
}

void Document::visualUpdatesSuppressionTimerFired()
Expand Down Expand Up @@ -8298,6 +8299,42 @@ void Document::clearScriptedAnimationController()
scriptedAnimationController->clearDocumentPointer();
}

// https://html.spec.whatwg.org/multipage/dom.html#render-blocking-mechanism
bool Document::allowsAddingRenderBlockedElements() const
{
return contentType() == "text/html"_s && !body();
}

bool Document::isRenderBlocked() const
{
return m_visualUpdatesPreventedReasons.contains(VisualUpdatesPreventedReason::RenderBlocking);
}

void Document::blockRenderingOn(Element& element)
{
if (allowsAddingRenderBlockedElements()) {
m_renderBlockingElements.add(element);
addVisualUpdatePreventedReason(VisualUpdatesPreventedReason::RenderBlocking);
}
}

void Document::unblockRenderingOn(Element& element)
{
m_renderBlockingElements.remove(element);
if (m_renderBlockingElements.isEmptyIgnoringNullReferences())
removeVisualUpdatePreventedReasons(VisualUpdatesPreventedReason::RenderBlocking);
}

// https://html.spec.whatwg.org/multipage/links.html#process-internal-resource-links
void Document::processInternalResourceLinks(HTMLAnchorElement& anchor)
{
auto copy = copyToVector(m_renderBlockingElements);
for (auto& element : copy) {
if (RefPtr link = dynamicDowncast<HTMLLinkElement>(element.get()))
link->processInternalResourceLink(&anchor);
}
}

CheckedRef<FrameSelection> Document::checkedSelection()
{
return m_selection.get();
Expand Down
12 changes: 11 additions & 1 deletion Source/WebCore/dom/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ class Frame;
class GPUCanvasContext;
class GraphicsClient;
class HTMLAllCollection;
class HTMLAnchorElement;
class HTMLAttachmentElement;
class HTMLBaseElement;
class HTMLBodyElement;
Expand Down Expand Up @@ -1824,6 +1825,12 @@ class Document
void updateServiceWorkerClientData() final;
WEBCORE_EXPORT void navigateFromServiceWorker(const URL&, CompletionHandler<void(ScheduleLocationChangeResult)>&&);

bool allowsAddingRenderBlockedElements() const;
bool isRenderBlocked() const;
void blockRenderingOn(Element&);
void unblockRenderingOn(Element&);
void processInternalResourceLinks(HTMLAnchorElement&);

#if ENABLE(VIDEO)
WEBCORE_EXPORT void forEachMediaElement(const Function<void(HTMLMediaElement&)>&);
#endif
Expand Down Expand Up @@ -2061,8 +2068,9 @@ class Document
Client = 1 << 0,
ReadyState = 1 << 1,
Suspension = 1 << 2,
RenderBlocking = 1 << 3,
};
static constexpr OptionSet<VisualUpdatesPreventedReason> visualUpdatePreventReasonsClearedByTimer() { return { VisualUpdatesPreventedReason::ReadyState }; }
static constexpr OptionSet<VisualUpdatesPreventedReason> visualUpdatePreventReasonsClearedByTimer() { return { VisualUpdatesPreventedReason::ReadyState, VisualUpdatesPreventedReason::RenderBlocking }; }
static constexpr OptionSet<VisualUpdatesPreventedReason> visualUpdatePreventRequiresLayoutMilestones() { return { VisualUpdatesPreventedReason::Client, VisualUpdatesPreventedReason::ReadyState }; }

void addVisualUpdatePreventedReason(VisualUpdatesPreventedReason);
Expand Down Expand Up @@ -2452,6 +2460,8 @@ class Document

WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_elementsWithPendingUserAgentShadowTreeUpdates;

WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_renderBlockingElements;

RefPtr<ReportingScope> m_reportingScope;

std::unique_ptr<WakeLockManager> m_wakeLockManager;
Expand Down
10 changes: 9 additions & 1 deletion Source/WebCore/html/HTMLAnchorElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ void HTMLAnchorElement::attributeChanged(const QualifiedName& name, const AtomSt
m_linkRelations.add(Relation::Opener);
if (m_relList)
m_relList->associatedAttributeValueChanged();
}
} else if (name == nameAttr)
document().processInternalResourceLinks(*this);
}

bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
Expand Down Expand Up @@ -746,4 +747,11 @@ ReferrerPolicy HTMLAnchorElement::referrerPolicy() const
return parseReferrerPolicy(attributeWithoutSynchronization(referrerpolicyAttr), ReferrerPolicySource::ReferrerPolicyAttribute).value_or(ReferrerPolicy::EmptyString);
}

Node::InsertedIntoAncestorResult HTMLAnchorElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
{
auto result = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
document().processInternalResourceLinks(*this);
return result;
}

}
2 changes: 2 additions & 0 deletions Source/WebCore/html/HTMLAnchorElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class HTMLAnchorElement : public HTMLElement, public URLDecomposition {
String referrerPolicyForBindings() const;
ReferrerPolicy referrerPolicy() const;

Node::InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode& parentOfInsertedTree) override;

protected:
HTMLAnchorElement(const QualifiedName&, Document&);

Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/html/HTMLAttributeNames.in
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ axis
background
behavior
bgcolor
blocking
border
bordercolor
capture
Expand Down
Loading

0 comments on commit 0e9bbd9

Please sign in to comment.