LibWeb: Avoid full viewport resize style invalidations
Use the viewport metric dependency flags to restyle only elements whose computed values can change after a viewport resize. Descendants that inherit changed values are reached through the existing inherited-style update path. Keep targeted style reads correct by treating pending media query evaluation as style dirtiness. Seed the style computer with the latest viewport before resolving pending animated style, so viewport-unit keyframes do not use stale metrics. Share pseudo-element recomputation with inherited-style updates, so pseudos stay current when their originating element changes only via inherited values. Schedule animated style updates when inherited style recomputation can affect existing animations. Add viewport resize coverage for media queries, inherited font metrics, monospace font-size recascade, line-height percentages, font-relative and pending viewport-unit animations, inherited pseudo-elements, direct pseudo viewport dependencies, and canvas currentColor reads.
This commit is contained in:
parent
b4ef1e9c15
commit
ceb25e10d4
11 changed files with 470 additions and 62 deletions
|
|
@ -2108,6 +2108,9 @@ void Document::update_style()
|
|||
if (!browsing_context())
|
||||
return;
|
||||
|
||||
// Fetch the viewport rect once, instead of repeatedly, during style computation.
|
||||
style_computer().set_viewport_rect({}, viewport_rect());
|
||||
|
||||
update_animated_style_if_needed();
|
||||
|
||||
// Associated with each top-level browsing context is a current transition generation that is incremented on each
|
||||
|
|
@ -2119,21 +2122,21 @@ void Document::update_style()
|
|||
CSS::Invalidation::invalidate_style_for_pending_has_mutations(*this);
|
||||
}
|
||||
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update() && !m_needs_media_query_evaluation)
|
||||
return;
|
||||
|
||||
m_style_invalidator->invalidate(*this);
|
||||
|
||||
// NOTE: If this is a document hosting <template> contents, style update is unnecessary.
|
||||
if (m_created_for_appropriate_template_contents)
|
||||
return;
|
||||
|
||||
// Fetch the viewport rect once, instead of repeatedly, during style computation.
|
||||
style_computer().set_viewport_rect({}, viewport_rect());
|
||||
|
||||
if (m_needs_media_query_evaluation)
|
||||
evaluate_media_rules();
|
||||
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
||||
return;
|
||||
|
||||
m_style_invalidator->invalidate(*this);
|
||||
|
||||
build_registered_properties_cache();
|
||||
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
|
@ -2154,6 +2157,37 @@ void Document::update_style()
|
|||
apply_document_style_invalidation_after_style_change(*this, invalidation);
|
||||
}
|
||||
|
||||
static bool element_or_pseudo_depends_on_viewport_metrics(Element const& element)
|
||||
{
|
||||
if (auto computed_properties = element.computed_properties(); computed_properties && computed_properties->depends_on_viewport_metrics())
|
||||
return true;
|
||||
|
||||
bool depends_on_viewport_metrics = false;
|
||||
element.for_each_synthetic_pseudo_element([&](CSS::PseudoElement, SyntheticPseudoElement const& pseudo_element) {
|
||||
if (auto computed_properties = pseudo_element.computed_properties(); computed_properties && computed_properties->depends_on_viewport_metrics()) {
|
||||
depends_on_viewport_metrics = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return depends_on_viewport_metrics;
|
||||
}
|
||||
|
||||
void Document::invalidate_style_for_viewport_change()
|
||||
{
|
||||
for_each_shadow_including_inclusive_descendant([](Node& node) {
|
||||
auto* element = as_if<Element>(node);
|
||||
if (!element)
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
// Descendants that inherit changed values are reached by the normal inherited-style invalidation path.
|
||||
if (element_or_pseudo_depends_on_viewport_metrics(*element) || element->style_uses_if_css_function())
|
||||
element->set_needs_style_update(true);
|
||||
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void Document::update_style_if_needed_for_element(AbstractElement const& abstract_element)
|
||||
{
|
||||
if (element_needs_style_update(abstract_element))
|
||||
|
|
@ -2207,10 +2241,10 @@ GC::Ptr<CSS::ComputedProperties const> Document::update_style_for_element(Abstra
|
|||
navigable->container()->document().update_layout(UpdateLayoutReason::ChildDocumentStyleUpdate);
|
||||
|
||||
if (browsing_context()) {
|
||||
update_animated_style_if_needed();
|
||||
|
||||
style_computer().set_viewport_rect({}, viewport_rect());
|
||||
|
||||
update_animated_style_if_needed();
|
||||
|
||||
// Media query evaluation can enqueue normal style invalidations, so do it before deciding whether the full
|
||||
// style traversal needs to run.
|
||||
if (m_needs_media_query_evaluation)
|
||||
|
|
@ -2337,6 +2371,8 @@ bool Document::element_needs_style_update(AbstractElement const& abstract_elemen
|
|||
return true;
|
||||
if (m_needs_invalidation_of_elements_affected_by_has)
|
||||
return true;
|
||||
if (m_needs_media_query_evaluation)
|
||||
return true;
|
||||
if (m_style_invalidator->has_pending_invalidations())
|
||||
return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ public:
|
|||
void obtain_theme_color();
|
||||
|
||||
void update_style();
|
||||
void invalidate_style_for_viewport_change();
|
||||
void update_style_if_needed_for_element(AbstractElement const&);
|
||||
enum class StyleUpdateMode : u8 {
|
||||
Normal,
|
||||
|
|
|
|||
|
|
@ -944,6 +944,67 @@ static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(C
|
|||
return invalidation;
|
||||
}
|
||||
|
||||
CSS::RequiredInvalidationAfterStyleChange Element::recompute_pseudo_element_styles(bool& did_change_custom_properties, bool had_list_marker)
|
||||
{
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
||||
auto& style_computer = document().style_computer();
|
||||
|
||||
// Any document change that can cause this element's style to change, could also affect its pseudo-elements.
|
||||
auto recompute_pseudo_element_style = [&](CSS::PseudoElement pseudo_element) {
|
||||
style_computer.push_ancestor(*this);
|
||||
|
||||
auto pseudo_element_style = computed_properties(pseudo_element);
|
||||
auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed({ *this, pseudo_element }, did_change_custom_properties);
|
||||
|
||||
// TODO: Can we be smarter about invalidation?
|
||||
if (pseudo_element_style && new_pseudo_element_style) {
|
||||
DOM::AbstractElement abstract_element { *this, pseudo_element };
|
||||
invalidation |= compute_required_invalidation(*pseudo_element_style, *new_pseudo_element_style, document().font_computer(), pseudo_element_unsafe_layout_node(pseudo_element), abstract_element);
|
||||
} else if (pseudo_element_style || new_pseudo_element_style) {
|
||||
invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
set_computed_properties(pseudo_element, move(new_pseudo_element_style));
|
||||
style_computer.pop_ancestor(*this);
|
||||
};
|
||||
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Before);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::After);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::FirstLetter);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Selection);
|
||||
if (m_rendered_in_top_layer)
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Backdrop);
|
||||
if (had_list_marker || m_computed_properties->display().is_list_item())
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Marker);
|
||||
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
void Element::apply_computed_style_to_layout_node_if_needed(CSS::RequiredInvalidationAfterStyleChange const& invalidation)
|
||||
{
|
||||
if (invalidation.rebuild_layout_tree || !unsafe_layout_node())
|
||||
return;
|
||||
|
||||
// If we're keeping the layout tree, we can just apply the new style to the existing layout tree.
|
||||
unsafe_layout_node()->apply_style(*m_computed_properties);
|
||||
if (invalidation.repaint)
|
||||
set_needs_repaint();
|
||||
|
||||
// Do the same for pseudo-elements.
|
||||
for_each_synthetic_pseudo_element([&](CSS::PseudoElement pseudo_element_type, SyntheticPseudoElement const& pseudo_element) {
|
||||
auto pseudo_element_style = computed_properties(pseudo_element_type);
|
||||
if (!pseudo_element_style)
|
||||
return;
|
||||
|
||||
if (auto node_with_style = pseudo_element.unsafe_layout_node()) {
|
||||
node_with_style->apply_style(*pseudo_element_style);
|
||||
if (invalidation.repaint && node_with_style->first_paintable())
|
||||
node_with_style->first_paintable()->set_needs_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_change_custom_properties)
|
||||
{
|
||||
VERIFY(parent());
|
||||
|
|
@ -1022,58 +1083,14 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_cha
|
|||
});
|
||||
}
|
||||
|
||||
// Any document change that can cause this element's style to change, could also affect its pseudo-elements.
|
||||
auto recompute_pseudo_element_style = [&](CSS::PseudoElement pseudo_element) {
|
||||
style_computer.push_ancestor(*this);
|
||||
|
||||
auto pseudo_element_style = computed_properties(pseudo_element);
|
||||
auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed({ *this, pseudo_element }, did_change_custom_properties);
|
||||
|
||||
// TODO: Can we be smarter about invalidation?
|
||||
if (pseudo_element_style && new_pseudo_element_style) {
|
||||
DOM::AbstractElement abstract_element { *this, pseudo_element };
|
||||
invalidation |= compute_required_invalidation(*pseudo_element_style, *new_pseudo_element_style, document().font_computer(), pseudo_element_unsafe_layout_node(pseudo_element), abstract_element);
|
||||
} else if (pseudo_element_style || new_pseudo_element_style) {
|
||||
invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
set_computed_properties(pseudo_element, move(new_pseudo_element_style));
|
||||
style_computer.pop_ancestor(*this);
|
||||
};
|
||||
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Before);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::After);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::FirstLetter);
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Selection);
|
||||
if (m_rendered_in_top_layer)
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Backdrop);
|
||||
if (had_list_marker || m_computed_properties->display().is_list_item())
|
||||
recompute_pseudo_element_style(CSS::PseudoElement::Marker);
|
||||
invalidation |= recompute_pseudo_element_styles(did_change_custom_properties, had_list_marker);
|
||||
|
||||
if (invalidation.is_none()) {
|
||||
counters.element_style_noop_recomputations++;
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
if (!invalidation.rebuild_layout_tree && unsafe_layout_node()) {
|
||||
// If we're keeping the layout tree, we can just apply the new style to the existing layout tree.
|
||||
unsafe_layout_node()->apply_style(*m_computed_properties);
|
||||
if (invalidation.repaint)
|
||||
set_needs_repaint();
|
||||
|
||||
// Do the same for pseudo-elements.
|
||||
for_each_synthetic_pseudo_element([&](CSS::PseudoElement pseudo_element_type, SyntheticPseudoElement const& pseudo_element) {
|
||||
auto pseudo_element_style = computed_properties(pseudo_element_type);
|
||||
if (!pseudo_element_style)
|
||||
return;
|
||||
|
||||
if (auto node_with_style = pseudo_element.unsafe_layout_node()) {
|
||||
node_with_style->apply_style(*pseudo_element_style);
|
||||
if (invalidation.repaint && node_with_style->first_paintable())
|
||||
node_with_style->first_paintable()->set_needs_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
apply_computed_style_to_layout_node_if_needed(invalidation);
|
||||
|
||||
return invalidation;
|
||||
}
|
||||
|
|
@ -1085,6 +1102,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style()
|
|||
|
||||
auto computed_properties = this->computed_properties();
|
||||
VERIFY(computed_properties);
|
||||
auto had_list_marker = computed_properties->display().is_list_item();
|
||||
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
||||
|
|
@ -1134,17 +1152,18 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style()
|
|||
invalidation |= CSS::compute_property_invalidation(property_id, old_value.ptr(), &new_value);
|
||||
}
|
||||
|
||||
if (!invalidation.is_none() && !computed_properties->animated_property_values().is_empty())
|
||||
document().set_needs_animated_style_update();
|
||||
|
||||
bool did_change_custom_properties = false;
|
||||
invalidation |= recompute_pseudo_element_styles(did_change_custom_properties, had_list_marker);
|
||||
|
||||
if (invalidation.is_none()) {
|
||||
counters.element_inherited_style_noop_recomputations++;
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
// NB: unsafe_layout_node() because we're applying recomputed inherited styles during
|
||||
// style recalculation, before layout has been updated.
|
||||
if (unsafe_layout_node())
|
||||
unsafe_layout_node()->apply_style(*computed_properties);
|
||||
if (invalidation.repaint)
|
||||
set_needs_repaint();
|
||||
apply_computed_style_to_layout_node_if_needed(invalidation);
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -668,6 +668,8 @@ private:
|
|||
FlyString make_html_uppercased_qualified_name() const;
|
||||
|
||||
void exit_fullscreen_on_element_removal();
|
||||
CSS::RequiredInvalidationAfterStyleChange recompute_pseudo_element_styles(bool& did_change_custom_properties, bool had_list_marker);
|
||||
void apply_computed_style_to_layout_node_if_needed(CSS::RequiredInvalidationAfterStyleChange const&);
|
||||
|
||||
WebIDL::ExceptionOr<GC::Ptr<Node>> insert_adjacent(StringView where, GC::Ref<Node> node);
|
||||
|
||||
|
|
|
|||
|
|
@ -2938,8 +2938,7 @@ void Navigable::set_viewport_size(CSSPixelSize size, InvalidateDisplayList inval
|
|||
}
|
||||
|
||||
if (auto document = active_document()) {
|
||||
// NOTE: Resizing the viewport changes the reference value for viewport-relative CSS lengths.
|
||||
document->invalidate_style(DOM::StyleInvalidationReason::NavigableSetViewportSize);
|
||||
document->invalidate_style_for_viewport_change();
|
||||
document->set_needs_media_query_evaluation();
|
||||
document->set_needs_layout_update(DOM::SetNeedsLayoutReason::NavigableSetViewportSize);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
initial viewport animated width: 45px
|
||||
initial inherited-font animated width: 45px
|
||||
initial root-font animated width: 45px
|
||||
initial animated font size: 45px
|
||||
initial animated inherited-font width: 67.5px
|
||||
targeted pending animated outline width: 75px
|
||||
resized viewport animated width: 75px
|
||||
resized inherited-font animated width: 75px
|
||||
resized root-font animated width: 75px
|
||||
resized animated font size: 75px
|
||||
resized animated inherited-font width: 112.5px
|
||||
full invalidations: 0
|
||||
style recomputations bounded: true
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
initial color: rgb(255, 0, 0)
|
||||
initial canvas fill style: #ff0000
|
||||
targeted canvas fill style: #008000
|
||||
resized color: rgb(0, 128, 0)
|
||||
media query full invalidations: 1
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
viewport width: 50px
|
||||
inherited font size: 50px
|
||||
monospace recascaded font size: 50px
|
||||
percentage font size: 75px
|
||||
percentage em width: 75px
|
||||
percentage line height: 75px
|
||||
calc percentage line height: 67.5px
|
||||
explicit inherit width: 50px
|
||||
pseudo width: 50px
|
||||
pseudo originating font size: 50px
|
||||
pseudo inherited font size: 50px
|
||||
full invalidations: 0
|
||||
style recomputations bounded: true
|
||||
inherited recomputations present: true
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../../include.js"></script>
|
||||
<script>
|
||||
function animationFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
asyncTest(async done => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.border = "0";
|
||||
iframe.style.width = "300px";
|
||||
iframe.style.height = "200px";
|
||||
|
||||
const loaded = new Promise(resolve => iframe.addEventListener("load", resolve, { once: true }));
|
||||
iframe.srcdoc = `
|
||||
<!DOCTYPE html>
|
||||
<style>
|
||||
html {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#font-source {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#animated-font-source {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
<div id="viewport-target"></div>
|
||||
<div id="font-source"><div id="inherited-font-target"></div></div>
|
||||
<div id="root-font-target"></div>
|
||||
<div id="animated-font-source"><div id="animated-inherited-font-target"></div></div>
|
||||
<div id="pending-outline-animation-target"></div>
|
||||
<script>
|
||||
function pauseHalfway(animation) {
|
||||
animation.pause();
|
||||
animation.currentTime = 500;
|
||||
}
|
||||
|
||||
pauseHalfway(document.getElementById("viewport-target").animate([{ width: "10vw" }, { width: "20vw" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
}));
|
||||
pauseHalfway(document.getElementById("inherited-font-target").animate([{ width: "1em" }, { width: "2em" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
}));
|
||||
pauseHalfway(document.getElementById("root-font-target").animate([{ width: "1rem" }, { width: "2rem" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
}));
|
||||
pauseHalfway(document.getElementById("animated-font-source").animate([{ fontSize: "10vw" }, { fontSize: "20vw" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
}));
|
||||
pauseHalfway(document.getElementById("animated-inherited-font-target").animate([{ width: "1em" }, { width: "2em" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
}));
|
||||
<\/script>
|
||||
`;
|
||||
document.body.appendChild(iframe);
|
||||
await loaded;
|
||||
|
||||
const child = iframe.contentWindow;
|
||||
const viewportTarget = child.document.getElementById("viewport-target");
|
||||
const inheritedFontTarget = child.document.getElementById("inherited-font-target");
|
||||
const rootFontTarget = child.document.getElementById("root-font-target");
|
||||
const animatedFontSource = child.document.getElementById("animated-font-source");
|
||||
const animatedInheritedFontTarget = child.document.getElementById("animated-inherited-font-target");
|
||||
const pendingOutlineAnimationTarget = child.document.getElementById("pending-outline-animation-target");
|
||||
|
||||
child.internals.updateStyle();
|
||||
println(`initial viewport animated width: ${child.getComputedStyle(viewportTarget).width}`);
|
||||
println(`initial inherited-font animated width: ${child.getComputedStyle(inheritedFontTarget).width}`);
|
||||
println(`initial root-font animated width: ${child.getComputedStyle(rootFontTarget).width}`);
|
||||
println(`initial animated font size: ${child.getComputedStyle(animatedFontSource).fontSize}`);
|
||||
println(`initial animated inherited-font width: ${child.getComputedStyle(animatedInheritedFontTarget).width}`);
|
||||
|
||||
const pendingOutlineAnimation = pendingOutlineAnimationTarget.animate([{ outlineWidth: "10vw" }, { outlineWidth: "20vw" }], {
|
||||
duration: 1000,
|
||||
fill: "both",
|
||||
});
|
||||
pendingOutlineAnimation.pause();
|
||||
pendingOutlineAnimation.currentTime = 500;
|
||||
|
||||
child.internals.resetStyleInvalidationCounters();
|
||||
iframe.style.width = "500px";
|
||||
document.body.offsetWidth;
|
||||
child.internals.updateStyle();
|
||||
println(`targeted pending animated outline width: ${child.getComputedStyle(pendingOutlineAnimationTarget).outlineWidth}`);
|
||||
await animationFrame();
|
||||
child.document.body.offsetWidth;
|
||||
child.internals.updateStyle();
|
||||
|
||||
const counters = child.internals.getStyleInvalidationCounters();
|
||||
const elementCount = child.document.querySelectorAll("*").length;
|
||||
println(`resized viewport animated width: ${child.getComputedStyle(viewportTarget).width}`);
|
||||
println(`resized inherited-font animated width: ${child.getComputedStyle(inheritedFontTarget).width}`);
|
||||
println(`resized root-font animated width: ${child.getComputedStyle(rootFontTarget).width}`);
|
||||
println(`resized animated font size: ${child.getComputedStyle(animatedFontSource).fontSize}`);
|
||||
println(`resized animated inherited-font width: ${child.getComputedStyle(animatedInheritedFontTarget).width}`);
|
||||
println(`full invalidations: ${counters.fullStyleInvalidations}`);
|
||||
println(`style recomputations bounded: ${counters.elementStyleRecomputations < elementCount}`);
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../../include.js"></script>
|
||||
<script>
|
||||
function animationFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
asyncTest(async done => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.border = "0";
|
||||
iframe.style.width = "300px";
|
||||
iframe.style.height = "200px";
|
||||
|
||||
const loaded = new Promise(resolve => iframe.addEventListener("load", resolve, { once: true }));
|
||||
iframe.srcdoc = `
|
||||
<!DOCTYPE html>
|
||||
<style>
|
||||
#target {
|
||||
color: rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
#canvas-target {
|
||||
color: rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
@media (min-width: 400px) {
|
||||
#target,
|
||||
#canvas-target {
|
||||
color: rgb(0, 128, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="target">target</div>
|
||||
<canvas id="canvas-target"></canvas>
|
||||
`;
|
||||
document.body.appendChild(iframe);
|
||||
await loaded;
|
||||
|
||||
const child = iframe.contentWindow;
|
||||
const target = child.document.getElementById("target");
|
||||
const canvasTarget = child.document.getElementById("canvas-target");
|
||||
const context = canvasTarget.getContext("2d");
|
||||
|
||||
child.internals.updateStyle();
|
||||
println(`initial color: ${child.getComputedStyle(target).color}`);
|
||||
context.fillStyle = "currentColor";
|
||||
println(`initial canvas fill style: ${context.fillStyle}`);
|
||||
|
||||
child.internals.resetStyleInvalidationCounters();
|
||||
iframe.style.width = "500px";
|
||||
document.body.offsetWidth;
|
||||
context.fillStyle = "currentColor";
|
||||
println(`targeted canvas fill style: ${context.fillStyle}`);
|
||||
|
||||
await animationFrame();
|
||||
child.document.body.offsetWidth;
|
||||
child.internals.updateStyle();
|
||||
|
||||
const counters = child.internals.getStyleInvalidationCounters();
|
||||
println(`resized color: ${child.getComputedStyle(target).color}`);
|
||||
println(`media query full invalidations: ${counters.fullStyleInvalidations}`);
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../../include.js"></script>
|
||||
<script>
|
||||
function animationFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
function childDocumentSource() {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<style>
|
||||
#viewport {
|
||||
width: 10vw;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
#font-source {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#monospace-ancestor {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#monospace-target {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#percentage-font-source {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
#percentage-font-target {
|
||||
width: 1em;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#line-height-source {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#line-height-percentage {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
#line-height-calc {
|
||||
line-height: calc(125% + 5px);
|
||||
}
|
||||
|
||||
#inherit-source {
|
||||
width: 10vw;
|
||||
}
|
||||
|
||||
#inherit-child {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
#pseudo::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 10vw;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#pseudo-font-source {
|
||||
font-size: 10vw;
|
||||
}
|
||||
|
||||
#pseudo-font-target::before {
|
||||
content: "pseudo";
|
||||
}
|
||||
|
||||
.bystander {
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
<div id="viewport"></div>
|
||||
<div id="font-source"><span id="font-child">font child</span></div>
|
||||
<div id="monospace-ancestor"><span id="monospace-target">monospace target</span></div>
|
||||
<div id="percentage-font-source"><div id="percentage-font-target"></div></div>
|
||||
<div id="line-height-source">
|
||||
<div id="line-height-percentage"></div>
|
||||
<div id="line-height-calc"></div>
|
||||
</div>
|
||||
<div id="inherit-source"><div id="inherit-child"></div></div>
|
||||
<div id="pseudo"></div>
|
||||
<div id="pseudo-font-source"><span id="pseudo-font-target"></span></div>
|
||||
<script>
|
||||
for (let i = 0; i < 40; ++i) {
|
||||
const bystander = document.createElement("div");
|
||||
bystander.className = "bystander";
|
||||
bystander.textContent = "bystander " + i;
|
||||
document.body.appendChild(bystander);
|
||||
}
|
||||
<\/script>
|
||||
`;
|
||||
}
|
||||
|
||||
asyncTest(async done => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.border = "0";
|
||||
iframe.style.width = "300px";
|
||||
iframe.style.height = "200px";
|
||||
|
||||
const loaded = new Promise(resolve => iframe.addEventListener("load", resolve, { once: true }));
|
||||
iframe.srcdoc = childDocumentSource();
|
||||
document.body.appendChild(iframe);
|
||||
await loaded;
|
||||
|
||||
const child = iframe.contentWindow;
|
||||
child.internals.updateStyle();
|
||||
child.document.body.offsetWidth;
|
||||
|
||||
child.internals.resetStyleInvalidationCounters();
|
||||
iframe.style.width = "500px";
|
||||
document.body.offsetWidth;
|
||||
await animationFrame();
|
||||
child.document.body.offsetWidth;
|
||||
child.internals.updateStyle();
|
||||
|
||||
const counters = child.internals.getStyleInvalidationCounters();
|
||||
const elementCount = child.document.querySelectorAll("*").length;
|
||||
|
||||
println(`viewport width: ${child.getComputedStyle(child.document.getElementById("viewport")).width}`);
|
||||
println(`inherited font size: ${child.getComputedStyle(child.document.getElementById("font-child")).fontSize}`);
|
||||
println(`monospace recascaded font size: ${child.getComputedStyle(child.document.getElementById("monospace-target")).fontSize}`);
|
||||
println(`percentage font size: ${child.getComputedStyle(child.document.getElementById("percentage-font-source")).fontSize}`);
|
||||
println(`percentage em width: ${child.getComputedStyle(child.document.getElementById("percentage-font-target")).width}`);
|
||||
println(`percentage line height: ${child.getComputedStyle(child.document.getElementById("line-height-percentage")).lineHeight}`);
|
||||
println(`calc percentage line height: ${child.getComputedStyle(child.document.getElementById("line-height-calc")).lineHeight}`);
|
||||
println(`explicit inherit width: ${child.getComputedStyle(child.document.getElementById("inherit-child")).width}`);
|
||||
println(`pseudo width: ${child.getComputedStyle(child.document.getElementById("pseudo"), "::before").width}`);
|
||||
println(`pseudo originating font size: ${child.getComputedStyle(child.document.getElementById("pseudo-font-target")).fontSize}`);
|
||||
println(`pseudo inherited font size: ${child.getComputedStyle(child.document.getElementById("pseudo-font-target"), "::before").fontSize}`);
|
||||
println(`full invalidations: ${counters.fullStyleInvalidations}`);
|
||||
println(`style recomputations bounded: ${counters.elementStyleRecomputations < elementCount / 2}`);
|
||||
println(`inherited recomputations present: ${counters.elementInheritedStyleRecomputations > 0}`);
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue