Bug 1733042 - Use scrollport size rather than content-box size of scrollframes. r=boris

I don't really feel our behavior is particularly less correct than
Chromium since in our implementation an scrolling element has two boxes
rather than one, but in the interest of interop, and given developers
find it useful, it seems worth doing this.

Differential Revision: https://phabricator.services.mozilla.com/D139551
This commit is contained in:
Emilio Cobos Álvarez 2022-02-24 12:13:08 +00:00
Родитель 0c6c038a4f
Коммит c939c657eb
3 изменённых файлов: 102 добавлений и 38 удалений

Просмотреть файл

@ -11,6 +11,7 @@
#include "mozilla/SVGUtils.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIScrollableFrame.h"
#include <limits>
namespace mozilla::dom {
@ -45,6 +46,14 @@ static uint32_t GetNodeDepth(nsINode* aNode) {
return depth;
}
static nsSize GetContentRectSize(const nsIFrame& aFrame) {
if (const nsIScrollableFrame* f = do_QueryFrame(&aFrame)) {
// We return the scrollport rect for compat with other UAs, see bug 1733042.
return f->GetScrollPortRect().Size();
}
return aFrame.GetContentRectRelativeToSelf().Size();
}
/**
* Returns |aTarget|'s size in the form of gfx::Size (in pixels).
* If the target is SVG, width and height are determined from bounding box.
@ -75,47 +84,42 @@ static gfx::Size CalculateBoxSize(Element* aTarget,
const LayoutDeviceIntSize snappedSize =
RoundedToInt(CSSSize::FromUnknownSize(size) *
frame->PresContext()->CSSToDevPixelScale());
size = gfx::Size(snappedSize.ToUnknownSize());
}
} else {
// Per the spec, non-replaced inline Elements will always have an empty
// 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.)
if (!frame->IsFrameOfType(nsIFrame::eReplaced) &&
frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
return size;
}
switch (aBox) {
case ResizeObserverBoxOptions::Border_box:
// GetSize() includes the content area, borders, and padding.
size = CSSPixel::FromAppUnits(frame->GetSize()).ToUnknownSize();
break;
case ResizeObserverBoxOptions::Device_pixel_content_box: {
// This is a implementation-dependent for subpixel snapping algorithm.
// Gecko relys on LayoutDevicePixel to convert (and snap) the app units
// into device pixels in painting and gfx code, so here we simply
// convert it into dev pixels and round it.
//
// Note: This size must contain integer values.
// https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box
const LayoutDeviceIntSize snappedSize =
LayoutDevicePixel::FromAppUnitsRounded(
frame->GetContentRectRelativeToSelf().Size(),
frame->PresContext()->AppUnitsPerDevPixel());
size = gfx::Size(snappedSize.ToUnknownSize());
break;
}
case ResizeObserverBoxOptions::Content_box:
default:
size =
CSSPixel::FromAppUnits(frame->GetContentRectRelativeToSelf().Size())
.ToUnknownSize();
return gfx::Size(snappedSize.ToUnknownSize());
}
return size;
}
return size;
// Per the spec, non-replaced inline Elements will always have an empty
// 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.)
if (!frame->IsFrameOfType(nsIFrame::eReplaced) &&
frame->IsFrameOfType(nsIFrame::eLineParticipant)) {
return size;
}
switch (aBox) {
case ResizeObserverBoxOptions::Border_box:
return CSSPixel::FromAppUnits(frame->GetSize()).ToUnknownSize();
case ResizeObserverBoxOptions::Device_pixel_content_box: {
// This is a implementation-dependent for subpixel snapping algorithm.
// Gecko relies on LayoutDevicePixel to convert (and snap) the app units
// into device pixels in painting and gfx code, so here we simply
// convert it into dev pixels and round it.
//
// Note: This size must contain integer values.
// https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box
const LayoutDeviceIntSize snappedSize =
LayoutDevicePixel::FromAppUnitsRounded(
GetContentRectSize(*frame),
frame->PresContext()->AppUnitsPerDevPixel());
return gfx::Size(snappedSize.ToUnknownSize());
}
case ResizeObserverBoxOptions::Content_box:
default:
break;
}
return CSSPixel::FromAppUnits(GetContentRectSize(*frame)).ToUnknownSize();
}
NS_IMPL_CYCLE_COLLECTION_CLASS(ResizeObservation)

Просмотреть файл

@ -0,0 +1,4 @@
[scrollbars.html]
bug: expected to fail with overlay scrollbars
expected:
if os == "android": FAIL

Просмотреть файл

@ -0,0 +1,56 @@
<!DOCTYPE html>
<title>ResizeObserver content-box size and scrollbars</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1733042">
<style>
#outer {
position: relative;
width: 100px;
height: 200px;
overflow: auto;
background: #818182;
}
#inner {
position: absolute;
top: 0;
left: 0;
width: 10px;
height: 10px;
background: #0a6fc0;
}
</style>
<div id="outer">
<div id="inner"></div>
</div>
<script>
async function animationFrame() {
return new Promise(r => requestAnimationFrame(r));
}
// This test is expected to fail with overlay scrollbars.
promise_test(async function() {
let count = 0;
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const observer = new ResizeObserver(entries => {
count++;
});
observer.observe(outer);
inner.style.top = '1000px';
await animationFrame();
await animationFrame();
inner.style.top = 0;
await animationFrame();
await animationFrame();
assert_equals(count, 2, "ResizeObserver should subtract scrollbar sizes from content-box rect");
});
</script>