Skip to content

Commit

Permalink
Bug 1791375 - Handle multiple fragments in ResizeObserver. r=emilio
Browse files Browse the repository at this point in the history
As per CSSWG resolution: w3c/csswg-drafts#3673

Some details are not clear, so implement it behind a pref, disabled by
default.

Differential Revision: https://phabricator.services.mozilla.com/D157641
  • Loading branch information
Loirooriol committed Sep 21, 2022
1 parent 2f8e897 commit eb7550f
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 1 deletion.
17 changes: 16 additions & 1 deletion dom/base/ResizeObserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,17 @@ static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
nsIFrame* frame = aTarget->GetPrimaryFrame();

if (!frame) {
// TODO: Should this return an empty array instead?
// https://github.com/w3c/csswg-drafts/issues/7734
return {LogicalPixelSize()};
}

if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
// Per the spec, this target's SVG size is always its bounding box size no
// matter what box option you choose, because SVG elements do not use
// standard CSS box model.
// TODO: what if the SVG is fragmented?
// https://github.com/w3c/csswg-drafts/issues/7736
const gfxRect bbox = SVGUtils::GetBBox(frame);
gfx::Size size(static_cast<float>(bbox.width),
static_cast<float>(bbox.height));
Expand All @@ -106,6 +110,8 @@ static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
// content rect. Therefore, we always use the same trivially-empty size
// for non-replaced inline elements here, and their IsActive() will
// always return false. (So its observation won't be fired.)
// TODO: Should we use an empty array instead?
// https://github.com/w3c/csswg-drafts/issues/7734
if (!frame->IsFrameOfType(nsIFrame::eReplaced) &&
frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
return {LogicalPixelSize()};
Expand Down Expand Up @@ -151,7 +157,16 @@ static AutoTArray<LogicalPixelSize, 1> CalculateBoxSize(
}
return CSSPixel::FromAppUnits(GetContentRectSize(*frame)).ToUnknownSize();
};
return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))};
if (!StaticPrefs::dom_resize_observer_support_fragments()) {
return {LogicalPixelSize(frame->GetWritingMode(), GetFrameSize(frame))};
}
AutoTArray<LogicalPixelSize, 1> size;
while (frame) {
const WritingMode wm = frame->GetWritingMode();
size.AppendElement(LogicalPixelSize(wm, GetFrameSize(frame)));
frame = frame->GetNextContinuation();
}
return size;
}

NS_IMPL_CYCLE_COLLECTION_CLASS(ResizeObservation)
Expand Down
8 changes: 8 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4632,6 +4632,14 @@
value: true
mirror: always

# Let Resize Observer report the size of all fragments, and not just the
# first one, as per CSSWG resolution:
# https://github.com/w3c/csswg-drafts/issues/3673#issuecomment-467221565
- name: dom.resize_observer.support_fragments
type: bool
value: false
mirror: always

#---------------------------------------------------------------------------
# Prefs starting with "editor"
#---------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions testing/web-platform/meta/resize-observer/__dir__.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
prefs: [dom.resize_observer.support_fragments:true]
115 changes: 115 additions & 0 deletions testing/web-platform/tests/resize-observer/fragments.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>ResizeObserver with multiple fragments</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/resize-observer-1/">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/3673">
<meta name="assert" content="Tests ResizeObserver supports multiple fragments." />

<style>
#wrapper {
column-width: 100px;
width: max-content;
height: 100px;
margin: 10px;
}
#target {
outline: solid;
background: orange;
}
.w50 {
width: 50px;
}
.w75 {
width: 75px;
}
.h100 {
height: 100px;
}
.h150 {
height: 150px;
}
.h175 {
height: 175px;
}
</style>

<div id="log"></div>

<div id="wrapper">
<div id="target"></div>
</div>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const target = document.getElementById("target");

const nextSizes = (() => {
let callback = null;
new ResizeObserver((entries) => {
if (callback) {
callback(entries[0].contentBoxSize);
callback = null;
}
}).observe(target);
return () => {
if (callback) {
throw "Already awaiting another notification";
}
return new Promise((resolve, reject) => {
callback = resolve;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
reject("Missing ResizeObserver notification");
callback = null;
});
});
});
};
})();

function checkSizes(className, expectedSizes, msg) {
promise_test(async () => {
await new Promise(requestAnimationFrame);
target.className = className;
let sizes;
try {
sizes = await nextSizes();
} catch (error) {
assert_unreached(error);
}
assert_equals(sizes.length, expectedSizes.length, "number of fragments");
for (let i = 0; i < sizes.length; ++i) {
assert_equals(sizes[i].inlineSize, expectedSizes[i][0], `fragment #${i+1} inline size`);
assert_equals(sizes[i].blockSize, expectedSizes[i][1], `fragment #${i+1} block size`);
}
}, msg);
}

checkSizes(
"w50 h100",
[[50, 100]],
"Single fragment"
);
checkSizes(
"w50 h150",
[[50, 100], [50, 50]],
"Adding 2nd fragment"
);
checkSizes(
"w50 h175",
[[50, 100], [50, 75]],
"Resizing 2nd fragment"
);
checkSizes(
"w75 h175",
[[75, 100], [75, 75]],
"Resizing all fragments"
);
checkSizes(
"w75 h100",
[[75, 100]],
"Removing 2nd fragment"
);
</script>

0 comments on commit eb7550f

Please sign in to comment.