Bug 2056749 - Add -moz-scrollbar-inset-{block, inline} for improved scrollbar boundaries for rounded chrome corners. r=emilio,tabbrowser-reviewers,firefox-style-system-reviewers,layout-reviewers,sthompson

- Adds `-moz-scrollbar-inset-block` and `-moz-scrollbar-inset-inline`, chrome-only
  `<non-negative-length>{1,2}` longhands. Each names the scrollbar's axis it
  applies to. The two values are the axis' logical start and end
  and flip with the writing mode.
- ScrollContainerFrame::ScrollbarInsets() serves as the one place to extract the inset from for
    both nsScrollbarFrame and GetBorderRadii
- Applies inset to nsScrollbarFrame::Reflow similar to stepper buttons
- ScrollContainerFrame::GetBorderRadii no longer squares a corner off when the
  inset already holds the scrollbar clear of the curve.
- Forward the <browser> embedder's inset to a root scrollbar for the content viewport
- Add reftests for css-scrollbars
- This commit does NOT implement the css property anywhere

Differential Revision: https://phabricator.services.mozilla.com/D313900
This commit is contained in:
Michael Hynson
2026-09-02 16:03:53 +00:00
committed by mhynson@mozilla.com
parent 9c85b9b307
commit 19ded9cd7e
44 changed files with 940 additions and 59 deletions
@@ -160,6 +160,8 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
"scroll-snap-type",
"shape-rendering",
"scrollbar-gutter",
"-moz-scrollbar-inset-block",
"-moz-scrollbar-inset-inline",
"scrollbar-width",
"stroke-linecap",
"stroke-linejoin",
+9
View File
@@ -5,6 +5,7 @@
#include "mozilla/dom/BrowsingContext.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/GfxMessageUtils.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/DocAccessibleParent.h"
@@ -3492,6 +3493,14 @@ void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderScrollbarInset>,
LayoutDeviceIntMargin&& aOldValue) {
if (GetEmbedderScrollbarInset() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride aOldValue) {
MOZ_ASSERT(IsTop());
+13
View File
@@ -7,6 +7,7 @@
#include <tuple>
#include "GVAutoplayRequestUtils.h"
#include "Units.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HalScreenConfiguration.h"
#include "mozilla/LinkedList.h"
@@ -254,6 +255,10 @@ struct EmbedderColorSchemes {
/* prefers-color-scheme override based on the color-scheme style of our \
* <browser> embedder element. */ \
FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
/* Content-area scrollbar insets forwarded from the <browser> embedder's \
* -moz-scrollbar-inset-{block,inline}, so the top-level content viewport \
* scrollbars clear the rounded content-area corners. */ \
FIELD(EmbedderScrollbarInset, LayoutDeviceIntMargin) \
FIELD(DisplayMode, dom::DisplayMode) \
/* The number of entries added to the session history because of this \
* browsing context. */ \
@@ -1332,6 +1337,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
return CheckOnlyEmbedderCanSet(aSource);
}
bool CanSet(FieldIndex<IDX_EmbedderScrollbarInset>,
const LayoutDeviceIntMargin&, ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
}
bool CanSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride, ContentParent*) {
return IsTop();
@@ -1365,6 +1375,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
EmbedderColorSchemes&& aOldValue);
void DidSet(FieldIndex<IDX_EmbedderScrollbarInset>,
LayoutDeviceIntMargin&& aOldValue);
void DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride aOldValue);
+105 -37
View File
@@ -108,7 +108,7 @@ use.counter:
send_in_pings:
- use-counters
# Total of 2628 use counter metrics (excludes denominators).
# Total of 2632 use counter metrics (excludes denominators).
# Total of 416 'page' use counters.
use.counter.page:
svgsvgelement_getelementbyid:
@@ -19843,7 +19843,7 @@ use.counter.deprecated_ops.doc:
send_in_pings:
- use-counters
# Total of 734 'CSS (page)' use counters.
# Total of 736 'CSS (page)' use counters.
use.counter.css.page:
css_align_items:
type: counter
@@ -24860,6 +24860,40 @@ use.counter.css.page:
send_in_pings:
- use-counters
css_moz_scrollbar_inset_block:
type: counter
description: >
Whether a page used the CSS property -moz-scrollbar-inset-block.
Compare against `use.counter.top_level_content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_moz_scrollbar_inset_inline:
type: counter
description: >
Whether a page used the CSS property -moz-scrollbar-inset-inline.
Compare against `use.counter.top_level_content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_moz_window_transform:
type: counter
description: >
@@ -28736,6 +28770,23 @@ use.counter.css.page:
send_in_pings:
- use-counters
css_moz_appearance:
type: counter
description: >
Whether a page used the CSS property -moz-appearance.
Compare against `use.counter.top_level_content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_all:
type: counter
description: >
@@ -29110,23 +29161,6 @@ use.counter.css.page:
send_in_pings:
- use-counters
css_moz_appearance:
type: counter
description: >
Whether a page used the CSS property -moz-appearance.
Compare against `use.counter.top_level_content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_webkit_appearance:
type: counter
description: >
@@ -32323,7 +32357,7 @@ use.counter.css.page:
send_in_pings:
- use-counters
# Total of 734 'CSS (document)' use counters.
# Total of 736 'CSS (document)' use counters.
use.counter.css.doc:
css_align_items:
type: counter
@@ -37340,6 +37374,40 @@ use.counter.css.doc:
send_in_pings:
- use-counters
css_moz_scrollbar_inset_block:
type: counter
description: >
Whether a document used the CSS property -moz-scrollbar-inset-block.
Compare against `use.counter.content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_moz_scrollbar_inset_inline:
type: counter
description: >
Whether a document used the CSS property -moz-scrollbar-inset-inline.
Compare against `use.counter.content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_moz_window_transform:
type: counter
description: >
@@ -41216,6 +41284,23 @@ use.counter.css.doc:
send_in_pings:
- use-counters
css_moz_appearance:
type: counter
description: >
Whether a document used the CSS property -moz-appearance.
Compare against `use.counter.content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_all:
type: counter
description: >
@@ -41590,23 +41675,6 @@ use.counter.css.doc:
send_in_pings:
- use-counters
css_moz_appearance:
type: counter
description: >
Whether a document used the CSS property -moz-appearance.
Compare against `use.counter.content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
css_webkit_appearance:
type: counter
description: >
+19
View File
@@ -35,6 +35,7 @@
#include "mozilla/PresShellInlines.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/SMILAnimationController.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StaticPrefs_bidi.h"
@@ -896,6 +897,21 @@ void nsPresContext::SetLinkParametersOverride(
RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
}
void nsPresContext::SetEmbedderScrollbarInset(const nsMargin& aInset) {
if (mEmbedderScrollbarInset == aInset) {
return;
}
mEmbedderScrollbarInset = aInset;
// Read directly by nsScrollbarFrame::Reflow for the viewport scroll frame,
// so nothing needs re-cascading; just reflow the scrollbars.
if (mozilla::PresShell* presShell = GetPresShell()) {
if (ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame()) {
sf->MarkScrollbarsDirtyForReflow();
}
}
}
void nsPresContext::UpdateAnimationsPlayBackRateMultiplier(double aMultiplier) {
if (mAnimationsPlayBackRateMultiplier == aMultiplier) {
return;
@@ -939,6 +955,9 @@ void nsPresContext::RecomputeBrowsingContextDependentData() {
return browsingContext->GetEmbedderColorSchemes().mPreferred;
}());
SetEmbedderScrollbarInset(LayoutDevicePixel::ToAppUnits(
browsingContext->GetEmbedderScrollbarInset(), AppUnitsPerDevPixel()));
UpdateForcedColors();
SetInRDMPane(top->GetInRDMPane());
+10
View File
@@ -601,6 +601,15 @@ class nsPresContext : public nsISupports,
void SetLinkParametersOverride(
const mozilla::StyleLinkParameters& aLinkParameters);
/**
* Content-area scrollbar insets forwarded from the <browser> embedder, per
* physical side, in app units.
*/
const nsMargin& EmbedderScrollbarInset() const {
return mEmbedderScrollbarInset;
}
void SetEmbedderScrollbarInset(const nsMargin& aInset);
/**
* Return the device's screen size in inches, for font size
* inflation.
@@ -1433,6 +1442,7 @@ class nsPresContext : public nsISupports,
mozilla::dom::PrefersColorSchemeOverride mOverriddenOrEmbedderColorScheme;
mozilla::StyleForcedColors mForcedColors;
mozilla::StyleLinkParameters mLinkParameters;
nsMargin mEmbedderScrollbarInset;
protected:
virtual ~nsPresContext();
+56 -8
View File
@@ -5882,6 +5882,11 @@ GetWebkitScrollbarWidthAndHeight(
return {toSize(webkitScrollbarWidth), toSize(webkitScrollbarHeight)};
}
static nsMargin StyleScrollbarInsets(const ComputedStyle& aStyle,
const WritingMode aWm) {
return aStyle.StyleDisplay()->GetScrollbarInset(aWm).GetPhysicalMargin(aWm);
}
void ScrollContainerFrame::DidSetComputedStyle(
ComputedStyle* aOldComputedStyle) {
nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
@@ -5932,10 +5937,15 @@ void ScrollContainerFrame::DidSetComputedStyle(
}
}
if (aOldComputedStyle && !mIsRoot &&
StyleDisplay()->mScrollSnapType !=
aOldComputedStyle->StyleDisplay()->mScrollSnapType) {
PostPendingResnap();
if (aOldComputedStyle && !mIsRoot) {
if (StyleDisplay()->mScrollSnapType !=
aOldComputedStyle->StyleDisplay()->mScrollSnapType) {
PostPendingResnap();
}
if (ScrollbarInsets() !=
StyleScrollbarInsets(*aOldComputedStyle, GetWritingMode())) {
MarkScrollbarsDirtyForReflow();
}
}
}
@@ -6969,6 +6979,13 @@ void ScrollContainerFrame::LayoutScrollbars(ScrollReflowInput& aState,
}
}
nsMargin ScrollContainerFrame::ScrollbarInsets() const {
if (mIsRoot) {
return PresContext()->EmbedderScrollbarInset();
}
return StyleScrollbarInsets(*Style(), GetWritingMode());
}
static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nsSize& aRadius) {
// In order to ensure that the inside edge of the border has no
// curvature, we need at least one of its radii to be zero.
@@ -6982,6 +6999,20 @@ static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nsSize& aRadius) {
aRadius.width *= ratio;
aRadius.height *= ratio;
}
// Whether a scrollbar on one side of a corner reaches that corner's curve, and
// so forces it square.
// aInset: the scrollbar's inset at the given corner
// aRadius: the corner radius measured along the scrollbar's axis
// aBorder: border width on the side the curve starts from
static bool ScrollbarReachesCorner(bool aHasScrollbar, nscoord aInset,
nscoord aRadius, nscoord aBorder) {
if (!aHasScrollbar) {
return false;
}
// Subtracting aBorder because ReduceRadii only squares the border's inner
// edge; that also matches its early-out when the border swallows the radius.
return aInset < aRadius - aBorder;
}
/**
* Implement an override for nsIFrame::GetBorderRadii to ensure that
@@ -7006,16 +7037,33 @@ bool ScrollContainerFrame::GetBorderRadii(const nsSize& aFrameSize,
nsMargin sb = GetActualScrollbarSizes();
nsMargin border = GetUsedBorder();
if (sb.left > 0 || sb.top > 0) {
// A scrollbar with an inset holds clear of a corner leaves that corner's
// radius alone. A vertical scrollbar is inset at the top and bottom, and a
// horizontal one at the left and right.
const nsMargin inset = ScrollbarInsets();
if (ScrollbarReachesCorner(sb.left, inset.top, aRadii.TopLeft().height,
border.top) ||
ScrollbarReachesCorner(sb.top, inset.left, aRadii.TopLeft().width,
border.left)) {
ReduceRadii(border.left, border.top, aRadii.TopLeft());
}
if (sb.top > 0 || sb.right > 0) {
if (ScrollbarReachesCorner(sb.right, inset.top, aRadii.TopRight().height,
border.top) ||
ScrollbarReachesCorner(sb.top, inset.right, aRadii.TopRight().width,
border.right)) {
ReduceRadii(border.right, border.top, aRadii.TopRight());
}
if (sb.right > 0 || sb.bottom > 0) {
if (ScrollbarReachesCorner(sb.right, inset.bottom,
aRadii.BottomRight().height, border.bottom) ||
ScrollbarReachesCorner(sb.bottom, inset.right, aRadii.BottomRight().width,
border.right)) {
ReduceRadii(border.right, border.bottom, aRadii.BottomRight());
}
if (sb.bottom > 0 || sb.left > 0) {
if (ScrollbarReachesCorner(sb.left, inset.bottom, aRadii.BottomLeft().height,
border.bottom) ||
ScrollbarReachesCorner(sb.bottom, inset.left, aRadii.BottomLeft().width,
border.left)) {
ReduceRadii(border.left, border.bottom, aRadii.BottomLeft());
}
return true;
+2
View File
@@ -111,6 +111,8 @@ class ScrollContainerFrame : public nsContainerFrame,
nsIFrame::Sides aSkipSides,
nsRectCornerRadii&) const final;
nsMargin ScrollbarInsets() const;
nscoord IntrinsicISize(const IntrinsicSizeInput& aInput,
IntrinsicISizeType aType) override;
+8
View File
@@ -2314,6 +2314,14 @@ inline bool nsStyleMargin::HasAuto(
: HasBlockAxisAuto(aWM, aParams);
}
inline mozilla::LogicalMargin nsStyleDisplay::GetScrollbarInset(
mozilla::WritingMode aWM) const {
return mozilla::LogicalMargin(aWM, mScrollbarInsetBlock.start.ToAppUnits(),
mScrollbarInsetInline.end.ToAppUnits(),
mScrollbarInsetBlock.end.ToAppUnits(),
mScrollbarInsetInline.start.ToAppUnits());
}
inline AnchorResolvedMargin nsStyleMargin::GetMargin(
mozilla::LogicalSide aSide, mozilla::WritingMode aWM,
const AnchorPosResolutionParams& aParams) const {
+32
View File
@@ -177,6 +177,7 @@ void nsSubDocumentFrame::UpdateEmbeddedBrowsingContextDependentData() {
}
MaybeUpdateRemoteStyle();
MaybeUpdateEmbedderColorScheme();
MaybeUpdateEmbedderScrollbarInset();
MaybeUpdateEmbedderZoom();
PropagateIsUnderHiddenEmbedderElement(
PresShell()->IsUnderHiddenEmbedderElement() ||
@@ -775,6 +776,36 @@ void nsSubDocumentFrame::MaybeUpdateEmbedderColorScheme() {
(void)bc->SetEmbedderColorSchemes(schemes);
}
void nsSubDocumentFrame::MaybeUpdateEmbedderScrollbarInset() {
nsFrameLoader* fl = mFrameLoader.get();
if (!fl) {
return;
}
BrowsingContext* bc = fl->GetExtantBrowsingContext();
if (!bc) {
return;
}
// The content document can't express this in CSS: its viewport scroll frame's
// style is a non-inheriting anonymous box. Forward our own
// -moz-scrollbar-inset-{block,inline} instead, resolved to physical sides in
// our writing mode and converted to device pixels, since the subdocument's
// writing mode and zoom are both its own.
const WritingMode wm = GetWritingMode();
nsPresContext* pc = PresContext();
const nsMargin physical =
StyleDisplay()->GetScrollbarInset(wm).GetPhysicalMargin(wm);
const LayoutDeviceIntMargin inset = LayoutDevicePixel::FromAppUnitsRounded(
physical, pc->AppUnitsPerDevPixel());
if (bc->GetEmbedderScrollbarInset() == inset) {
return;
}
(void)bc->SetEmbedderScrollbarInset(inset);
}
void nsSubDocumentFrame::MaybeUpdateEmbedderZoom() {
nsFrameLoader* fl = mFrameLoader.get();
if (!fl) {
@@ -850,6 +881,7 @@ void nsSubDocumentFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
// If there's no old style, the call in Init() or ShowViewer() should have
// us covered.
MaybeUpdateEmbedderColorScheme();
MaybeUpdateEmbedderScrollbarInset();
MaybeUpdateRemoteStyle(aOldComputedStyle);
if (aOldComputedStyle->EffectiveZoom() != Style()->EffectiveZoom()) {
MaybeUpdateEmbedderZoom();
+1
View File
@@ -160,6 +160,7 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame,
friend class AsyncFrameInit;
void MaybeUpdateEmbedderColorScheme();
void MaybeUpdateEmbedderScrollbarInset();
void MaybeUpdateEmbedderZoom();
void MaybeUpdateRemoteStyle(ComputedStyle* aOldComputedStyle = nullptr);
void PropagateIsUnderHiddenEmbedderElement(bool aValue);
@@ -1,3 +1,40 @@
== viewport-scrollbar-color-change.html viewport-scrollbar-color-change-ref.html
== scrollbar-thin-overflow-change.html scrollbar-thin-overflow-change-ref.html
test-pref(layout.css.scrollbar-width-thin.disabled,true) == scrollbar-width-thin.html scrollbar-width-auto.html
# -moz-scrollbar-inset-{block,inline} are chrome-only, so these load over
# chrome://.
# Overlay scrollbars are forced off so the thumb is actually painted.
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-change.html chrome://reftest/content/css-scrollbars/scrollbar-inset-change-ref.html
# An embedder's inset must reach the subdocument's viewport scrollbar. The !=
# assertion fails if the forwarding does nothing, since the two iframes are
# otherwise identical.
pref(ui.useOverlayScrollbars,0) != chrome://reftest/content/css-scrollbars/scrollbar-inset-forward.html chrome://reftest/content/css-scrollbars/scrollbar-inset-forward-notref.html
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-forward-change.html chrome://reftest/content/css-scrollbars/scrollbar-inset-forward.html
# The inset is physical, so full-zooming the subdocument must not change it. The
# fuzz is the thumb snapping to device pixels at a different app-unit
# granularity; a CSS-px inset would be out by ~600 pixels, not 40. Verified: the
# same test at fullZoom 1.0 matches exactly.
pref(ui.useOverlayScrollbars,0) fuzzy(0-255,0-40) == chrome://reftest/content/css-scrollbars/scrollbar-inset-zoom.html chrome://reftest/content/css-scrollbars/scrollbar-inset-forward.html
# -moz-scrollbar-inset-inline must reach the horizontal scrollbar at all...
pref(ui.useOverlayScrollbars,0) != chrome://reftest/content/css-scrollbars/scrollbar-inset-inline.html chrome://reftest/content/css-scrollbars/scrollbar-inset-inline-notref.html
# ...and the logical start must resolve per writing mode, so an RTL inline-end
# inset matches an LTR inline-start one. Needs the != above to mean something.
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-rtl.html chrome://reftest/content/css-scrollbars/scrollbar-inset-inline.html
# An inset that holds the slider clear of the corner means GetBorderRadii no
# longer has to square that corner off, so the ring matches a container with no
# scrollbar at all. The cover hides the padding box, leaving only the ring.
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-radii.html chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-ref.html
# The same, but applying the inset dynamically, so the corner has to go back to
# being round after having been squared off.
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-change.html chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-ref.html
# Without an inset the corner must still be squared off. This also catches the
# == above passing for free if the scrollbar ever stops taking up layout space.
pref(ui.useOverlayScrollbars,0) != chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-notref.html chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-ref.html
# The corner GetBorderRadii spares must be picked per writing mode, so an RTL
# inline-end inset spares the same physical corner as an LTR inline-start one.
# Uses the inline axis because the block axis doesn't flip in RTL at all, and an
# asymmetric inset because a symmetric one is its own mirror image.
pref(ui.useOverlayScrollbars,0) == chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-rtl.html chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-inline.html
# The two ends must be distinguishable for the == above to mean anything: swap
# the inset and the opposite corner is spared instead.
pref(ui.useOverlayScrollbars,0) != chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-inline-notref.html chrome://reftest/content/css-scrollbars/scrollbar-inset-radii-inline.html
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
#outer {
overflow-y: scroll;
width: 200px;
height: 300px;
background: white;
-moz-scrollbar-inset-block: 40px 0;
}
#inner {
height: 900px;
}
</style>
<div id="outer">
<div id="inner"></div>
</div>
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<!-- Loaded from chrome:// so the chrome-only -moz-scrollbar-inset-block parses.
Applies the inset dynamically after a forced reflow; the reference declares
the final value up front. If changing -moz-scrollbar-inset-block doesn't reflow
the scrollbar, the thumb keeps its old travel range and this won't match. -->
<style>
#outer {
overflow-y: scroll;
width: 200px;
height: 300px;
background: white;
}
#outer.inset {
-moz-scrollbar-inset-block: 40px 0;
}
#inner {
height: 900px;
}
</style>
<div id="outer">
<div id="inner"></div>
</div>
<script>
// Force a reflow so the un-inset scrollbar geometry is really computed.
document.body.offsetHeight;
document.getElementById("outer").classList.add("inset");
</script>
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html class="reftest-wait">
<!-- Applies the embedder's inset after the subdocument has loaded and laid out.
Matches the statically-inset reference only if the change propagates and
reflows the subdocument's scrollbar, i.e. SetEmbedderScrollbarInset ->
MarkScrollbarsDirtyForReflow. Uses a class toggle because CSSOM
setProperty() is a silent no-op for chrome-only properties. -->
<style>
iframe {
width: 200px;
height: 300px;
border: none;
}
iframe.inset {
-moz-scrollbar-inset-block: 40px 0;
}
</style>
<iframe src="scrollbar-inset-forward-inner.html"></iframe>
<script>
const iframe = document.querySelector("iframe");
iframe.addEventListener("load", () => {
// Force the un-inset scrollbar geometry to be computed first.
iframe.contentDocument.documentElement.offsetHeight;
iframe.classList.add("inset");
requestAnimationFrame(() =>
requestAnimationFrame(() =>
document.documentElement.removeAttribute("class")));
});
</script>
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<!-- Inner document for the -moz-scrollbar-inset-block forwarding tests. Scrolls
vertically so its viewport scrollbar is present and painted. -->
<style>
html {
background: white;
scrollbar-color: black white;
}
body {
margin: 0;
/* Sized in vh, not px, so the scroll ratio - and therefore the thumb's
size in device pixels - is the same at any full zoom. That leaves the
inset as the only variable in the zoom test. */
height: 300vh;
}
</style>
<body></body>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<style>
iframe {
width: 200px;
height: 300px;
border: none;
}
</style>
<iframe src="scrollbar-inset-forward-inner.html"></iframe>
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<!-- -moz-scrollbar-inset-block on an embedder must reach the subdocument's viewport
scrollbar, which can't carry the property itself. Compared with != against
an identical iframe that has no inset: if the forwarding through
nsSubDocumentFrame -> BrowsingContext -> nsPresContext is broken, the two
render identically and this fails. -->
<style>
iframe {
width: 200px;
height: 300px;
border: none;
-moz-scrollbar-inset-block: 40px 0;
}
</style>
<iframe src="scrollbar-inset-forward-inner.html"></iframe>
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
#outer {
overflow-x: scroll;
overflow-y: hidden;
width: 300px;
height: 100px;
background: white;
scrollbar-color: black white;
}
#inner {
width: 900px;
height: 20px;
}
</style>
<div id="outer"><div id="inner"></div></div>
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<!-- -moz-scrollbar-inset-inline must reach the horizontal scrollbar. Compared
with != against an identical container that has no inset. -->
<style>
#outer {
overflow-x: scroll;
overflow-y: hidden;
width: 300px;
height: 100px;
background: white;
scrollbar-color: black white;
-moz-scrollbar-inset-inline: 40px 0;
}
#inner {
width: 900px;
height: 20px;
}
</style>
<div id="outer"><div id="inner"></div></div>
@@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<title>Applying an inset dynamically un-squares the corner</title>
<!-- Starts with a scrollbar and no inset, so GetBorderRadii squares the right
corners off, then applies the inset after a forced reflow. Matches the
no-scrollbar reference only if the corners go back to being round, which
needs the radii to be recomputed and the result repainted. -->
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-y: scroll;
}
.test.inset {
-moz-scrollbar-inset-block: 30px;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
<script>
// Force a reflow so the squared-off corners are really computed first.
document.body.offsetHeight;
document.querySelector(".test").classList.add("inset");
</script>
@@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<title>The mirrored inset keeps the bottom-right corner instead</title>
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-x: scroll;
overflow-y: hidden;
-moz-scrollbar-inset-inline: 0 30px;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<title>An inline-start inset keeps the bottom-left corner's radius</title>
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-x: scroll;
overflow-y: hidden;
-moz-scrollbar-inset-inline: 30px 0;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<title>Without an inset the corner is squared off</title>
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-y: scroll;
-moz-scrollbar-inset-block: 0;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<title>No scrollbar, so the radius is never reduced</title>
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow: hidden;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<title>In RTL the inline-end inset is the one that keeps the bottom-left corner</title>
<style>
.contain { position: relative; height: 140px }
/* Same physical result as -moz-scrollbar-inset-inline: 30px 0 in LTR, because
RTL puts the inline end on the left. A second writing-mode swap anywhere
between here and GetBorderRadii would protect the right corner instead. */
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-x: scroll;
overflow-y: hidden;
direction: rtl;
-moz-scrollbar-inset-inline: 0 30px;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<title>An inset that clears the corner keeps the border-radius</title>
<style>
.contain { position: relative; height: 140px }
.test {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100px;
border: 10px solid blue;
border-radius: 20px;
overflow-y: scroll;
-moz-scrollbar-inset-block: 30px;
}
/* Covers the whole padding box, so only the border ring is compared and the
scrollbar itself can't contribute pixels. */
.cover {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 100px;
background: blue;
}
</style>
<div class="contain">
<div class="test"></div>
<div class="cover"></div>
</div>
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<!-- The inset is logical, so in RTL the inline-start end is the physical right.
This declares the inset on the inline-END, which in RTL resolves to the
physical LEFT - the same physical result as the LTR reference declaring it on
the inline-start. Matches only if nsScrollbarFrame maps the logical start onto
a physical side and swaps the pair; without that swap the inset lands on the
right here and this fails.
scrollLeft is normalised so both documents have the thumb at the same end;
RTL otherwise starts scrolled to the opposite end from LTR. -->
<style>
#outer {
direction: rtl;
overflow-x: scroll;
overflow-y: hidden;
width: 300px;
height: 100px;
background: white;
scrollbar-color: black white;
-moz-scrollbar-inset-inline: 0 40px;
}
#inner {
width: 900px;
height: 20px;
}
</style>
<div id="outer"><div id="inner"></div></div>
<script>
// Scroll fully to the physical left in both writing modes.
const outer = document.getElementById("outer");
outer.scrollLeft = -(outer.scrollWidth - outer.clientWidth);
</script>
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html class="reftest-wait">
<!-- The forwarded inset must be a fixed physical size, so full-zooming the
subdocument must not change it. The reference is the same iframe at zoom 1.
This only isolates the inset because the subdocument is sized in vh: the
thumb's device-pixel size and the scrollbar's thickness are already
zoom-invariant. If the inset were carried in CSS pixels instead of device
pixels it would double at zoom 2 and the thumb would start lower. -->
<style>
iframe {
width: 200px;
height: 300px;
border: none;
-moz-scrollbar-inset-block: 40px 0;
}
</style>
<iframe src="scrollbar-inset-forward-inner.html"></iframe>
<script>
const iframe = document.querySelector("iframe");
iframe.addEventListener("load", () => {
iframe.frameLoader.browsingContext.fullZoom = 2.0;
requestAnimationFrame(() =>
requestAnimationFrame(() =>
document.documentElement.removeAttribute("class")));
});
</script>
+2
View File
@@ -582,6 +582,8 @@ def gen_css_properties_js(output):
"-moz-box-collapse", # chrome-only internal properties
"-moz-line-scroll-amount", # chrome-only internal properties
"-moz-image-decoding", # chrome-only internal properties
"-moz-scrollbar-inset-block", # chrome-only internal properties
"-moz-scrollbar-inset-inline", # chrome-only internal properties
"-moz-subtree-hidden-only-visually", # chrome-only internal properties
"-moz-user-focus", # chrome-only internal properties
"-moz-window-input-region-margin", # chrome-only internal properties
+1
View File
@@ -444,6 +444,7 @@ cbindgen-types = [
{ gecko = "StyleViewTransitionName", servo = "crate::values::computed::ViewTransitionName" },
{ gecko = "StyleResize", servo = "crate::values::computed::Resize" },
{ gecko = "StyleOverflowClipMargin", servo = "crate::values::computed::OverflowClipMargin" },
{ gecko = "StyleScrollbarInset", servo = "crate::values::computed::ScrollbarInset" },
{ gecko = "StyleFloat", servo = "crate::values::computed::Float" },
{ gecko = "StyleClear", servo = "crate::values::computed::Clear" },
{ gecko = "StyleOverscrollBehavior", servo = "crate::values::computed::OverscrollBehavior" },
+12 -1
View File
@@ -33,6 +33,7 @@
#include "nsCOMPtr.h"
#include "nsCRTGlue.h"
#include "nsCSSProps.h"
#include "nsChangeHint.h"
#include "nsContainerFrame.h"
#include "nsDeviceContext.h"
#include "nsIURI.h"
@@ -2264,6 +2265,8 @@ nsStyleDisplay::nsStyleDisplay()
mScrollSnapStop{StyleScrollSnapStop::Normal},
mScrollSnapType{StyleScrollSnapAxis::Both,
StyleScrollSnapStrictness::None},
mScrollbarInsetBlock{StyleLength{0.}, StyleLength{0.}},
mScrollbarInsetInline{StyleLength{0.}, StyleLength{0.}},
mBackfaceVisibility(StyleBackfaceVisibility::Visible),
mTransformStyle(StyleTransformStyle::Flat),
mTransformBox(StyleTransformBox::ViewBox),
@@ -2323,6 +2326,8 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
mScrollSnapAlign(aSource.mScrollSnapAlign),
mScrollSnapStop(aSource.mScrollSnapStop),
mScrollSnapType(aSource.mScrollSnapType),
mScrollbarInsetBlock(aSource.mScrollbarInsetBlock),
mScrollbarInsetInline(aSource.mScrollbarInsetInline),
mBackfaceVisibility(aSource.mBackfaceVisibility),
mTransformStyle(aSource.mTransformStyle),
mTransformBox(aSource.mTransformBox),
@@ -2736,6 +2741,10 @@ nsChangeHint nsStyleDisplay::CalcDifference(
// container-query selection on descendants).
// container-type / contain / content-visibility are handled by the
// mEffectiveContainment check.
//
// scrollbar-inset changes are dealt with in
// nsSubDocumentFrame::DidSetComputedStyle and
// ScrollContainerFrame::DidSetComputedStyle.
if (!hint && (mWillChange != aNewData.mWillChange ||
mOverflowAnchor != aNewData.mOverflowAnchor ||
mContentVisibility != aNewData.mContentVisibility ||
@@ -2743,7 +2752,9 @@ nsChangeHint nsStyleDisplay::CalcDifference(
mContain != aNewData.mContain ||
mContainerName != aNewData.mContainerName ||
mAnchorName != aNewData.mAnchorName ||
mAnchorScope != aNewData.mAnchorScope)) {
mAnchorScope != aNewData.mAnchorScope ||
mScrollbarInsetBlock != aNewData.mScrollbarInsetBlock ||
mScrollbarInsetInline != aNewData.mScrollbarInsetInline)) {
hint |= nsChangeHint_NeutralChange;
}
+9
View File
@@ -43,6 +43,7 @@ class ComputedStyle;
struct AnchorPosResolutionCache;
class AnchorPosReferenceData;
struct IntrinsicSize;
class LogicalMargin;
struct SizeComputationInput;
} // namespace mozilla
@@ -1662,6 +1663,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay {
mozilla::StyleScrollSnapAlign mScrollSnapAlign;
mozilla::StyleScrollSnapStop mScrollSnapStop;
mozilla::StyleScrollSnapType mScrollSnapType;
mozilla::StyleScrollbarInset mScrollbarInsetBlock;
mozilla::StyleScrollbarInset mScrollbarInsetInline;
mozilla::StyleBackfaceVisibility mBackfaceVisibility;
mozilla::StyleTransformStyle mTransformStyle;
@@ -1848,6 +1851,12 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay {
mOverflowY == mozilla::StyleOverflow::Visible;
}
// How much to shorten each scrollbar by at each of its ends. Each side holds
// the inset measured from it, so the block-axis pair shortens the scrollbar
// running along the block axis. Defined in WritingModes.h.
inline mozilla::LogicalMargin GetScrollbarInset(
mozilla::WritingMode aWM) const;
bool IsContainPaint() const {
// Short circuit for no containment whatsoever
if (!mEffectiveContainment) {
+18
View File
@@ -13899,6 +13899,24 @@ if (false) {
"2px",
],
};
gCSSProperties["-moz-scrollbar-inset-block"] = {
// domProp: "MozScrollbarInsetBlock",
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: ["0", "0 0"],
other_values: ["1px 2px", "calc(2em + 3ex)", "1px calc(2em)"],
invalid_values: ["auto", "20%", "-10px", "1px 2px 3px", "1px 2px 3px 4px"],
};
gCSSProperties["-moz-scrollbar-inset-inline"] = {
// domProp: "MozScrollbarInsetInline",
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: ["0", "0 0"],
other_values: ["1px 2px", "calc(2em + 3ex)", "1px calc(2em)"],
invalid_values: ["auto", "20%", "-10px", "1px 2px 3px", "1px 2px 3px 4px"],
};
}
gCSSProperties["scrollbar-color"] = {
+1
View File
@@ -9,6 +9,7 @@ reftest.jar:
content/bugs (../../reftests/bugs/*)
content/image (../../reftests/image/*)
content/css-display (../../reftests/css-display/*)
content/css-scrollbars (../../reftests/css-scrollbars/*)
content/fonts (../../reftests/fonts/*)
content/forms/input/file (../../reftests/forms/input/file/*)
content/forms/input/text (../../reftests/forms/input/text/*)
+31 -6
View File
@@ -124,6 +124,18 @@ void nsScrollbarFrame::Destroy(DestroyContext& aContext) {
nsContainerFrame::Destroy(aContext);
}
std::pair<nscoord, nscoord> nsScrollbarFrame::ScrollbarInset() const {
// The scroll container owns the property and resolves the writing mode, so
// the margin it hands back is already physical and needs no further mapping.
ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(GetParent());
if (!scrollContainerFrame) {
return {0, 0};
}
const nsMargin inset = scrollContainerFrame->ScrollbarInsets();
return IsHorizontal() ? std::pair{inset.left, inset.right}
: std::pair{inset.top, inset.bottom};
}
void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
@@ -152,11 +164,22 @@ void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
const nsSize containerSize = aDesiredSize.PhysicalSize();
const LogicalSize totalAvailSize = aDesiredSize.Size(wm);
LogicalPoint nextKidPos(wm);
MOZ_ASSERT(!wm.IsVertical());
const bool movesInInlineDirection = horizontal;
// We stay full-length so our track still spans the whole scrollport, and hold
// the slider back at each end instead. Unlike a scrollbar button the two ends
// are independent, so the symmetry assumption below doesn't apply to them.
const auto [insetStart, insetEnd] = ScrollbarInset();
LogicalPoint nextKidPos(wm);
if (movesInInlineDirection) {
nextKidPos.I(wm) = insetStart;
} else {
nextKidPos.B(wm) = insetStart;
}
// Layout our kids left to right / top to bottom.
for (nsIFrame* kid : mFrames) {
MOZ_ASSERT(!kid->GetWritingMode().IsOrthogonalTo(wm),
@@ -164,16 +187,18 @@ void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
const bool isSlider = kid->GetContent() == mSlider;
LogicalSize availSize = totalAvailSize;
{
const nscoord consumed =
movesInInlineDirection ? nextKidPos.I(wm) : nextKidPos.B(wm);
// Assume we'll consume the same size before and after the slider. This is
// not a technically correct assumption if we have weird scrollbar button
// setups, but those will be going away, see bug 1824254.
const int32_t factor = isSlider ? 2 : 1;
const nscoord reserved =
isSlider ? (consumed - insetStart) * 2 + insetStart + insetEnd
: consumed;
if (movesInInlineDirection) {
availSize.ISize(wm) =
std::max(0, totalAvailSize.ISize(wm) - nextKidPos.I(wm) * factor);
availSize.ISize(wm) = std::max(0, totalAvailSize.ISize(wm) - reserved);
} else {
availSize.BSize(wm) =
std::max(0, totalAvailSize.BSize(wm) - nextKidPos.B(wm) * factor);
availSize.BSize(wm) = std::max(0, totalAvailSize.BSize(wm) - reserved);
}
}
+2
View File
@@ -68,6 +68,8 @@ class nsScrollbarFrame final : public nsContainerFrame,
nsSize ScrollbarMinSize() const;
bool IsHorizontal() const;
std::pair<nscoord, nscoord> ScrollbarInset() const;
void Destroy(DestroyContext&) override;
void Init(nsIContent* aContent, nsContainerFrame* aParent,
@@ -4168,6 +4168,30 @@ animation_type = "discrete"
affects = "layout"
keyword = { values = ["auto", "thin", "none"] }
# Reserves space at each end of a vertical scrollbar.
[-moz-scrollbar-inset-block]
type = "ScrollbarInset"
initial = "computed::ScrollbarInset::zero()"
struct = "box"
engine = "gecko"
animation_type = "discrete"
enabled_in = "chrome"
gecko_ffi_name = "mScrollbarInsetBlock"
spec = "None (Nonstandard Firefox-only property)"
affects = "layout"
# Reserves space at each end of a horizontal scrollbar.
[-moz-scrollbar-inset-inline]
type = "ScrollbarInset"
initial = "computed::ScrollbarInset::zero()"
struct = "box"
engine = "gecko"
animation_type = "discrete"
enabled_in = "chrome"
gecko_ffi_name = "mScrollbarInsetInline"
spec = "None (Nonstandard Firefox-only property)"
affects = "layout"
[-moz-window-dragging]
struct = "ui"
engine = "gecko"
@@ -10,7 +10,7 @@ use crate::values::computed::length::{LengthPercentage, NonNegativeLength};
use crate::values::computed::{Context, Integer, Number, ToComputedValue};
use crate::values::generics::box_::{
GenericBaselineShift, GenericContainIntrinsicSize, GenericLineClamp, GenericOverflowClipMargin,
GenericPerspective,
GenericPerspective, GenericScrollbarInset,
};
use crate::values::generics::GreaterThanOrEqualToOne;
use crate::values::specified::box_ as specified;
@@ -31,6 +31,9 @@ pub type BaselineShift = GenericBaselineShift<LengthPercentage>;
/// A computed value for the `overflow-clip-margin` property.
pub type OverflowClipMargin = GenericOverflowClipMargin<NonNegativeLength>;
/// A computed value for the `-moz-scrollbar-inset-block` / `-inline` properties.
pub type ScrollbarInset = GenericScrollbarInset<NonNegativeLength>;
/// A computed value for the `contain-intrinsic-size` property.
pub type ContainIntrinsicSize = GenericContainIntrinsicSize<NonNegativeLength>;
@@ -61,8 +61,8 @@ pub use self::box_::{
Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display,
DominantBaseline, Float, LineClamp, MarginTrim, Overflow, OverflowAnchor, OverflowClipMargin,
OverscrollBehavior, Perspective, PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis,
ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange,
WritingModeProperty, Zoom,
ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, ScrollbarInset,
TouchAction, WillChange, WritingModeProperty, Zoom,
};
pub use self::color::{
Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust,
@@ -500,3 +500,53 @@ impl<L: Zero + ToCss> ToCss for OverflowClipMargin<L> {
Ok(())
}
}
/// The two insets of one scrollbar, for the chrome-only
/// `-moz-scrollbar-inset-block` / `-moz-scrollbar-inset-inline`. Each property
/// names the axis the scrollbar runs along, so `start` and `end` are the logical
/// start and end of that axis and flip with the writing mode.
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
ToTyped,
)]
#[repr(C)]
pub struct GenericScrollbarInset<L> {
/// The inset at the logical start of the axis.
pub start: L,
/// The inset at the logical end of the axis.
pub end: L,
}
pub use self::GenericScrollbarInset as ScrollbarInset;
impl<L: Zero> ScrollbarInset<L> {
/// Returns the initial, all-zero value.
pub fn zero() -> Self {
Self {
start: Zero::zero(),
end: Zero::zero(),
}
}
}
impl<L: PartialEq + ToCss> ToCss for ScrollbarInset<L> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.start.to_css(dest)?;
if self.end != self.start {
dest.write_char(' ')?;
self.end.to_css(dest)?;
}
Ok(())
}
}
+16 -2
View File
@@ -11,8 +11,8 @@ use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId};
pub use crate::typed_om::{KeywordValue, ToTyped, TypedValue};
use crate::values::generics::box_::{
BaselineShiftKeyword, BlockEllipsis, GenericBaselineShift, GenericContainIntrinsicSize,
GenericLineClamp, GenericOverflowClipMargin, GenericPerspective, MaxLines,
OverflowClipMarginBox,
GenericLineClamp, GenericOverflowClipMargin, GenericPerspective, GenericScrollbarInset,
MaxLines, OverflowClipMarginBox,
};
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
use crate::values::specified::{AllowQuirks, NonNegativeNumberOrPercentage, PositiveInteger};
@@ -117,6 +117,20 @@ impl Parse for OverflowClipMargin {
}
}
/// The specified value of `-moz-scrollbar-inset-block` / `-inline`.
pub type ScrollbarInset = GenericScrollbarInset<NonNegativeLength>;
impl Parse for ScrollbarInset {
// <length>{1,2}
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
let start = NonNegativeLength::parse(context, input)?;
let end = input
.try_parse(|i| NonNegativeLength::parse(context, i))
.unwrap_or_else(|_| start.clone());
Ok(Self { start, end })
}
}
/// Defines an elements display type, which consists of
/// the two basic qualities of how an element generates boxes
/// <https://drafts.csswg.org/css-display/#propdef-display>
@@ -45,8 +45,8 @@ pub use self::box_::{
Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display,
DominantBaseline, Float, LineClamp, MarginTrim, Overflow, OverflowAnchor, OverflowClipMargin,
OverscrollBehavior, Perspective, PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis,
ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange,
WillChangeBits, WritingModeProperty, Zoom,
ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, ScrollbarInset,
TouchAction, WillChange, WillChangeBits, WritingModeProperty, Zoom,
};
pub use self::calc::{CalcLengthPercentage, CalcNumeric};
pub use self::color::{
+1
View File
@@ -190,6 +190,7 @@ include = [
"ViewTimelineInset",
"OverflowAnchor",
"OverflowClipMargin",
"ScrollbarInset",
"Resize",
"Overflow",
"LengthPercentage",