Bug 1587973 - Part 3: Support device-pixel-content-box for ResizeObserver. r=emilio

This patch implements device-pixel-content-box for ResizeObserver.
GetTargetSize() returns CSS pixels for {border|content}-box, or device
pixels for device-pixel-content-box. We round the device pixel to
integral based on the spec,
https://drafts.csswg.org/resize-observer/#calculate-box-size.
And then we compare the current calculated box sizes and the last updated one
in IsActive().

Besides, the current wpts only use zoom property to verify this, but zoom
property is non-standard and we doesn't supports it, so now we only test the
getter functions for device-pixel-content-box and the subpixel snapping
algorithm (e.g. devicepixel.html) for Gecko in wpts.

Differential Revision: https://phabricator.services.mozilla.com/D120776
This commit is contained in:
Boris Chiou 2021-08-10 21:03:31 +00:00
Родитель 64e925cdb0
Коммит 6b574c5928
10 изменённых файлов: 171 добавлений и 71 удалений

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

@ -62,9 +62,19 @@ static gfx::Size GetTargetSize(Element* aTarget,
// Per the spec, 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.
gfxRect bbox = SVGUtils::GetBBox(frame);
const gfxRect bbox = SVGUtils::GetBBox(frame);
size.width = static_cast<float>(bbox.width);
size.height = static_cast<float>(bbox.height);
if (aBox == ResizeObserverBoxOptions::Device_pixel_content_box) {
// Per spec, we calculate the inline/block sizes to targets bounding box
// {inline|block} length, in integral device pixels, so we round the final
// result.
// https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box
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
@ -80,6 +90,21 @@ static gfx::Size GetTargetSize(Element* aTarget,
// 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 =
@ -295,8 +320,11 @@ uint32_t ResizeObserver::BroadcastActiveObservations() {
GetTargetSize(target, ResizeObserverBoxOptions::Border_box);
gfx::Size contentBoxSize =
GetTargetSize(target, ResizeObserverBoxOptions::Content_box);
gfx::Size devicePixelContentBoxSize = GetTargetSize(
target, ResizeObserverBoxOptions::Device_pixel_content_box);
RefPtr<ResizeObserverEntry> entry =
new ResizeObserverEntry(this, *target, borderBoxSize, contentBoxSize);
new ResizeObserverEntry(this, *target, borderBoxSize, contentBoxSize,
devicePixelContentBoxSize);
if (!entries.AppendElement(entry.forget(), fallible)) {
// Out of memory.
@ -309,6 +337,9 @@ uint32_t ResizeObserver::BroadcastActiveObservations() {
case ResizeObserverBoxOptions::Border_box:
observation->UpdateLastReportedSize(borderBoxSize);
break;
case ResizeObserverBoxOptions::Device_pixel_content_box:
observation->UpdateLastReportedSize(devicePixelContentBoxSize);
break;
case ResizeObserverBoxOptions::Content_box:
default:
observation->UpdateLastReportedSize(contentBoxSize);
@ -332,7 +363,8 @@ uint32_t ResizeObserver::BroadcastActiveObservations() {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverEntry, mOwner, mTarget,
mContentRect, mBorderBoxSize,
mContentBoxSize)
mContentBoxSize,
mDevicePixelContentBoxSize)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverEntry)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverEntry)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ResizeObserverEntry)
@ -364,6 +396,18 @@ void ResizeObserverEntry::GetContentBoxSize(
aRetVal.AppendElement(mContentBoxSize);
}
void ResizeObserverEntry::GetDevicePixelContentBoxSize(
nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const {
// In the resize-observer-1 spec, there will only be a single
// ResizeObserverSize returned in the FrozenArray for now.
//
// Note: the usage of FrozenArray is to support elements that have multiple
// fragments, which occur in multi-column scenarios.
// https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
aRetVal.Clear();
aRetVal.AppendElement(mDevicePixelContentBoxSize);
}
void ResizeObserverEntry::SetBorderBoxSize(const gfx::Size& aSize) {
nsIFrame* frame = mTarget->GetPrimaryFrame();
const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
@ -388,6 +432,12 @@ void ResizeObserverEntry::SetContentRectAndSize(const gfx::Size& aSize) {
mContentBoxSize = new ResizeObserverSize(this, aSize, wm);
}
void ResizeObserverEntry::SetDevicePixelContentSize(const gfx::Size& aSize) {
nsIFrame* frame = mTarget->GetPrimaryFrame();
const WritingMode wm = frame ? frame->GetWritingMode() : WritingMode();
mDevicePixelContentBoxSize = new ResizeObserverSize(this, aSize, wm);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ResizeObserverSize, mOwner)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizeObserverSize)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizeObserverSize)

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

@ -212,13 +212,15 @@ class ResizeObserverEntry final : public nsISupports, public nsWrapperCache {
ResizeObserverEntry(nsISupports* aOwner, Element& aTarget,
const gfx::Size& aBorderBoxSize,
const gfx::Size& aContentBoxSize)
const gfx::Size& aContentBoxSize,
const gfx::Size& aDevicePixelContentBoxSize)
: mOwner(aOwner), mTarget(&aTarget) {
MOZ_ASSERT(mOwner, "Need a non-null owner");
MOZ_ASSERT(mTarget, "Need a non-null target element");
SetBorderBoxSize(aBorderBoxSize);
SetContentRectAndSize(aContentBoxSize);
SetDevicePixelContentSize(aDevicePixelContentBoxSize);
}
nsISupports* GetParentObject() const { return mOwner; }
@ -237,11 +239,13 @@ class ResizeObserverEntry final : public nsISupports, public nsWrapperCache {
DOMRectReadOnly* ContentRect() const { return mContentRect; }
/**
* Returns target's logical border-box size and content-box size as
* ResizeObserverSize.
* Returns target's logical border-box size, content-box size, and
* device-pixel-content-box as an array of ResizeObserverSize.
*/
void GetBorderBoxSize(nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
void GetContentBoxSize(nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
void GetDevicePixelContentBoxSize(
nsTArray<RefPtr<ResizeObserverSize>>& aRetVal) const;
private:
~ResizeObserverEntry() = default;
@ -250,6 +254,8 @@ class ResizeObserverEntry final : public nsISupports, public nsWrapperCache {
void SetBorderBoxSize(const gfx::Size& aSize);
// Set contentRect and contentBoxSize.
void SetContentRectAndSize(const gfx::Size& aSize);
// Set devicePixelContentBoxSize.
void SetDevicePixelContentSize(const gfx::Size& aSize);
nsCOMPtr<nsISupports> mOwner;
nsCOMPtr<Element> mTarget;
@ -257,6 +263,7 @@ class ResizeObserverEntry final : public nsISupports, public nsWrapperCache {
RefPtr<DOMRectReadOnly> mContentRect;
RefPtr<ResizeObserverSize> mBorderBoxSize;
RefPtr<ResizeObserverSize> mContentBoxSize;
RefPtr<ResizeObserverSize> mDevicePixelContentBoxSize;
};
class ResizeObserverSize final : public nsISupports, public nsWrapperCache {

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

@ -9,7 +9,8 @@
enum ResizeObserverBoxOptions {
"border-box",
"content-box"
"content-box",
"device-pixel-content-box"
};
dictionary ResizeObserverOptions {
@ -42,6 +43,8 @@ interface ResizeObserverEntry {
readonly attribute sequence<ResizeObserverSize> borderBoxSize;
[Frozen, Cached, Pure]
readonly attribute sequence<ResizeObserverSize> contentBoxSize;
[Frozen, Cached, Pure]
readonly attribute sequence<ResizeObserverSize> devicePixelContentBoxSize;
};
[Pref="layout.css.resizeobserver.enabled",

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

@ -1,4 +1,3 @@
implementation-status: backlog
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1587973
[devicepixel.html]
expected: FAIL
fuzzy: maxDifference=0-2;totalPixels=0-1391
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1723618

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

@ -1,10 +1,4 @@
[idlharness.window.html]
[ResizeObserverEntry interface: entry must inherit property "devicePixelContentBoxSize" with the proper type]
expected: FAIL
[ResizeObserverEntry interface: attribute devicePixelContentBoxSize]
expected: FAIL
[ResizeObserverEntry must be primary interface of entry]
expected:
if (os == "linux") and debug and webrender and not fission: [PASS, FAIL]

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

@ -1,52 +1,46 @@
implementation-status: backlog
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1587973
[observe.html]
expected: TIMEOUT
expected:
if (os == "android") and webrender: ["TIMEOUT", "OK"]
[guard]
expected: NOTRUN
[test6: iframe notifications]
expected:
if os == "android": [FAIL, PASS]
[PASS, FAIL]
[test8: simple content-box observation]
expected:
if os == "linux": [FAIL, PASS]
if os == "android": FAIL
[test13: an observation is fired after the change of writing mode when box's specified size comes from physical size properties.]
expected:
if os == "win": [FAIL, PASS]
[test14: observe the same target but using a different box should override the previous one]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [FAIL, PASS]
[test18: an observation is fired when device-pixel-content-box is being observed]
expected: FAIL
[test8: simple content-box observation]
expected:
if os == "win": [FAIL, PASS]
if os == "linux": [PASS, FAIL]
[test10: simple border-box observation]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [FAIL, PASS]
[test17: Box sizing snd Resize Observer notifications]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [FAIL, PASS]
if (os == "android") and webrender: ["NOTRUN", "PASS"]
[test5: observe img]
expected:
if os == "win": [PASS, FAIL]
[test6: iframe notifications]
expected:
if os == "android": [FAIL, PASS]
[test8: simple content-box observation]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [FAIL, PASS]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1723619
[test9: simple content-box observation but keep border-box size unchanged]
expected:
if os == "win": [FAIL, PASS]
[test10: simple border-box observation]
expected:
if os == "linux": [PASS, FAIL]
if os == "win": [FAIL, PASS]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1723619
[test13: an observation is fired after the change of writing mode when box's specified size comes from physical size properties.]
expected:
if os == "win": [FAIL, PASS]
[test14: observe the same target but using a different box should override the previous one]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [PASS, FAIL]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1723619
[test17: Box sizing snd Resize Observer notifications]
expected:
if os == "linux": [FAIL, PASS]
if os == "win": [FAIL, PASS]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1723619

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

@ -1,14 +1,5 @@
implementation-status: backlog
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1587973
[svg.html]
expected: TIMEOUT
[test15: observe svg:text content and border box]
expected:
if os == "mac": FAIL
[guard]
expected: NOTRUN
[test16: observe g:rect content, border and device-pixel-content boxes]
expected: FAIL

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

@ -46,7 +46,7 @@
// Use a linewidth of 2. Because the rectangle is drawn at 0,0 with
// its dimensions being the same as canvas dimensions, linewidth as it
// is drawn on the canvas will be 1.
ctx.lineWidth = "2";
ctx.lineWidth = window.devicePixelRatio * 2;
ctx.strokeStyle = "green";
ctx.rect(0, 0, snappedSize.inlineSize, snappedSize.blockSize);
ctx.stroke();

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

@ -878,6 +878,34 @@ function test18() {
"target device-pixel-content-box block size");
}
},
]);
return helper.start(() => t.remove());
}
function test19() {
// zoom is not a standard css property, so we should check it first. If the
// browser doesn't support it, we skip this test.
if (!CSS.supports("zoom", "3")) {
return Promise.resolve();
}
let t = createAndAppendElement("div");
t.style.height = "100px";
t.style.width = "50px";
let helper = new ResizeTestHelper(
"test19: an observation is fired when device-pixel-content-box is being " +
"observed and zoom change",
[
{
setup: observer => {
observer.observe(t, {box: "device-pixel-content-box"});
},
notify: entries => {
// No need to test again (see test18), so skip this event loop.
}
},
{
setup: observer => {
document.body.style.zoom = 3;
@ -931,6 +959,7 @@ test0()
.then(() => test16())
.then(() => test17())
.then(() => test18())
.then(() => test19())
.then(() => guard.done());
</script>

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

@ -505,7 +505,6 @@ function test16() {
setup: observer => {
observer.observe(target, {box: "device-pixel-content-box"});
target.setAttribute('width', 50);
document.body.style.zoom = 0.1;
},
notify: (entries, observer) => {
assert_equals(entries.length, 1);
@ -515,16 +514,49 @@ function test16() {
assert_equals(entries[0].contentBoxSize[0].blockSize, 20);
assert_equals(entries[0].borderBoxSize[0].inlineSize, 50);
assert_equals(entries[0].borderBoxSize[0].blockSize, 20);
assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 5);
assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 2);
assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 50);
assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 20);
return true; // Delay next step
}
},
{
setup: observer => {
observer.observe(target, {box: "device-pixel-content-box"});
target.setAttribute('height', 30);
},
notify: (entries, observer) => {
assert_equals(entries.length, 1);
assert_equals(entries[0].contentRect.width, 50);
assert_equals(entries[0].contentRect.height, 30);
assert_equals(entries[0].contentBoxSize[0].inlineSize, 50);
assert_equals(entries[0].contentBoxSize[0].blockSize, 30);
assert_equals(entries[0].borderBoxSize[0].inlineSize, 50);
assert_equals(entries[0].borderBoxSize[0].blockSize, 30);
assert_equals(entries[0].devicePixelContentBoxSize[0].inlineSize, 50);
assert_equals(entries[0].devicePixelContentBoxSize[0].blockSize, 30);
}
}
]);
return helper.start();
}
function test17() {
// zoom is not a standard css property, so we should check it first. If the
// browser doesn't support it, we skip this test.
if (!CSS.supports("zoom", "0.1")) {
return Promise.resolve();
}
let target = document.querySelector('#g_rect');
let helper = new ResizeTestHelper(
"test17: observe g:rect content, border and device-pixel-content boxes with zoom",
[
{
setup: observer => {
observer.observe(target, {box: "device-pixel-content-box"});
target.setAttribute('width', 50);
target.setAttribute('height', 30);
document.body.style.zoom = 0.1;
},
notify: (entries, observer) => {
assert_equals(entries.length, 1);
assert_equals(entries[0].contentRect.width, 50);
@ -581,6 +613,7 @@ test0()
.then(() => { return test14(); })
.then(() => { return test15(); })
.then(() => { return test16(); })
.then(() => { return test17(); })
.then(() => { guard.done(); });
</script>