fune/layout/xul/nsScrollbarFrame.cpp
Emilio Cobos Álvarez a7401488af Bug 1784265 - Drop support for flex attribute values other than 0 and 1. r=dholbert,mconley,preferences-reviewers
This makes it easier to get parity between legacy and regular flex
without having to either have tons of arbitrary attribute selectors in
the xul sheet, nor adding attribute lookup hacks to the html flexbox
layout.

Also, reimplement the remaining supported flex attribute-values (0 and 1)
purely in terms of CSS rules in xul.css (regardless of whether
emulate-moz-box-with-flex is enabled).

In practice these are pretty uncommon and the style attribute does the
trick in every case I've tried.

Add a debug-only assertion to ensure we preserve behavior for now.

Add a new test with another behavior difference between flexbox
emulation and old xul layout because the old reftest now passes. Use
replaced elements, which in modern flex are treated differently.

Differential Revision: https://phabricator.services.mozilla.com/D154394
2022-08-12 23:13:41 +00:00

546 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//
// Eric Vaughan
// Netscape Communications
//
// See documentation in associated header file
//
#include "nsScrollbarFrame.h"
#include "nsSliderFrame.h"
#include "nsScrollbarButtonFrame.h"
#include "nsContentCreatorFunctions.h"
#include "nsGkAtoms.h"
#include "nsIScrollableFrame.h"
#include "nsIScrollbarMediator.h"
#include "nsStyleConsts.h"
#include "nsIContent.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/StaticPrefs_apz.h"
using namespace mozilla;
using mozilla::dom::Element;
//
// NS_NewScrollbarFrame
//
// Creates a new scrollbar frame and returns it
//
nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
return new (aPresShell)
nsScrollbarFrame(aStyle, aPresShell->GetPresContext());
}
NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame)
NS_QUERYFRAME_HEAD(nsScrollbarFrame)
NS_QUERYFRAME_ENTRY(nsScrollbarFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
void nsScrollbarFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) {
nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
// We want to be a reflow root since we use reflows to move the
// slider. Any reflow inside the scrollbar frame will be a reflow to
// move the slider and will thus not change anything outside of the
// scrollbar or change the size of the scrollbar frame.
AddStateBits(NS_FRAME_REFLOW_ROOT);
}
void nsScrollbarFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
aPostDestroyData.AddAnonymousContent(mUpTopButton.forget());
aPostDestroyData.AddAnonymousContent(mDownTopButton.forget());
aPostDestroyData.AddAnonymousContent(mSlider.forget());
aPostDestroyData.AddAnonymousContent(mUpBottomButton.forget());
aPostDestroyData.AddAnonymousContent(mDownBottomButton.forget());
nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
// nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure
// our desired size agrees.
if (aReflowInput.AvailableWidth() == 0) {
aDesiredSize.Width() = 0;
}
if (aReflowInput.AvailableHeight() == 0) {
aDesiredSize.Height() = 0;
}
}
nsresult nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType) {
nsresult rv =
nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
// Update value in our children
UpdateChildrenAttributeValue(aAttribute, true);
// if the current position changes, notify any nsGfxScrollFrame
// parent we may have
if (aAttribute != nsGkAtoms::curpos) return rv;
nsIScrollableFrame* scrollable = do_QueryFrame(GetParent());
if (!scrollable) return rv;
nsCOMPtr<nsIContent> content(mContent);
scrollable->CurPosAttributeChanged(content);
return rv;
}
NS_IMETHODIMP
nsScrollbarFrame::HandlePress(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
return NS_OK;
}
NS_IMETHODIMP
nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus,
bool aControlHeld) {
return NS_OK;
}
NS_IMETHODIMP
nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
return NS_OK;
}
NS_IMETHODIMP
nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
return NS_OK;
}
void nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) {
mScrollbarMediator = aMediator;
}
nsIScrollbarMediator* nsScrollbarFrame::GetScrollbarMediator() {
if (!mScrollbarMediator) {
return nullptr;
}
nsIFrame* f = mScrollbarMediator->GetPrimaryFrame();
nsIScrollableFrame* scrollFrame = do_QueryFrame(f);
nsIScrollbarMediator* sbm;
if (scrollFrame) {
nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
sbm = do_QueryFrame(scrolledFrame);
if (sbm) {
return sbm;
}
}
sbm = do_QueryFrame(f);
if (f && !sbm) {
f = f->PresShell()->GetRootScrollFrame();
if (f && f->GetContent() == mScrollbarMediator) {
return do_QueryFrame(f);
}
}
return sbm;
}
nsresult nsScrollbarFrame::GetXULMargin(nsMargin& aMargin) {
aMargin.SizeTo(0, 0, 0, 0);
const bool overlayScrollbars = PresContext()->UseOverlayScrollbars();
const bool horizontal = IsXULHorizontal();
bool didSetMargin = false;
if (overlayScrollbars) {
nsSize minSize;
bool widthSet = false;
bool heightSet = false;
AddXULMinSize(this, minSize, widthSet, heightSet);
if (horizontal) {
if (heightSet) {
aMargin.top = -minSize.height;
didSetMargin = true;
}
} else {
if (widthSet) {
aMargin.left = -minSize.width;
didSetMargin = true;
}
}
}
if (!didSetMargin) {
DebugOnly<nsresult> rv = nsIFrame::GetXULMargin(aMargin);
// TODO(emilio): Should probably not be fallible, it's not like anybody
// cares about the return value anyway.
MOZ_ASSERT(NS_SUCCEEDED(rv), "nsIFrame::GetXULMargin can't really fail");
}
if (!horizontal) {
nsIScrollbarMediator* scrollFrame = GetScrollbarMediator();
if (scrollFrame && !scrollFrame->IsScrollbarOnRight()) {
std::swap(aMargin.left, aMargin.right);
}
}
return NS_OK;
}
void nsScrollbarFrame::SetIncrementToLine(int32_t aDirection) {
mSmoothScroll = true;
mDirection = aDirection;
mScrollUnit = ScrollUnit::LINES;
// get the scrollbar's content node
nsIContent* content = GetContent();
mIncrement = aDirection * nsSliderFrame::GetIncrement(content);
}
void nsScrollbarFrame::SetIncrementToPage(int32_t aDirection) {
mSmoothScroll = true;
mDirection = aDirection;
mScrollUnit = ScrollUnit::PAGES;
// get the scrollbar's content node
nsIContent* content = GetContent();
mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content);
}
void nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection) {
// Don't repeat or use smooth scrolling if scrolling to beginning or end
// of a page.
mSmoothScroll = false;
mDirection = aDirection;
mScrollUnit = ScrollUnit::WHOLE;
// get the scrollbar's content node
nsIContent* content = GetContent();
if (aDirection == -1)
mIncrement = -nsSliderFrame::GetCurrentPosition(content);
else
mIncrement = nsSliderFrame::GetMaxPosition(content) -
nsSliderFrame::GetCurrentPosition(content);
}
int32_t nsScrollbarFrame::MoveToNewPosition(
ImplementsScrollByUnit aImplementsScrollByUnit) {
if (aImplementsScrollByUnit == ImplementsScrollByUnit::Yes &&
StaticPrefs::apz_scrollbarbuttonrepeat_enabled()) {
nsIScrollbarMediator* m = GetScrollbarMediator();
MOZ_ASSERT(m);
// aImplementsScrollByUnit being Yes indicates the caller doesn't care
// about the return value.
// Note that this `MoveToNewPosition` is used for scrolling triggered by
// repeating scrollbar button press, so we'd use an intended-direction
// scroll snap flag.
m->ScrollByUnit(
this, mSmoothScroll ? ScrollMode::Smooth : ScrollMode::Instant,
mDirection, mScrollUnit, ScrollSnapFlags::IntendedDirection);
return 0;
}
// get the scrollbar's content node
RefPtr<Element> content = GetContent()->AsElement();
// get the current pos
int32_t curpos = nsSliderFrame::GetCurrentPosition(content);
// get the max pos
int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
// increment the given amount
if (mIncrement) {
curpos += mIncrement;
}
// make sure the current position is between the current and max positions
if (curpos < 0) {
curpos = 0;
} else if (curpos > maxpos) {
curpos = maxpos;
}
// set the current position of the slider.
nsAutoString curposStr;
curposStr.AppendInt(curpos);
AutoWeakFrame weakFrame(this);
if (mSmoothScroll) {
content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, u"true"_ns, false);
}
content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false);
// notify the nsScrollbarFrame of the change
AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos,
dom::MutationEvent_Binding::MODIFICATION);
if (!weakFrame.IsAlive()) {
return curpos;
}
// notify all nsSliderFrames of the change
for (const auto& childList : ChildLists()) {
for (nsIFrame* f : childList.mList) {
nsSliderFrame* sliderFrame = do_QueryFrame(f);
if (sliderFrame) {
sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos,
dom::MutationEvent_Binding::MODIFICATION);
if (!weakFrame.IsAlive()) {
return curpos;
}
}
}
}
content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
return curpos;
}
static already_AddRefed<Element> MakeScrollbarButton(
dom::NodeInfo* aNodeInfo, bool aVertical, bool aBottom, bool aDown,
AnonymousContentKey& aKey) {
MOZ_ASSERT(aNodeInfo);
MOZ_ASSERT(
aNodeInfo->Equals(nsGkAtoms::scrollbarbutton, nullptr, kNameSpaceID_XUL));
static constexpr nsLiteralString kSbattrValues[2][2] = {
{
u"scrollbar-up-top"_ns,
u"scrollbar-up-bottom"_ns,
},
{
u"scrollbar-down-top"_ns,
u"scrollbar-down-bottom"_ns,
},
};
static constexpr nsLiteralString kTypeValues[2] = {
u"decrement"_ns,
u"increment"_ns,
};
aKey = AnonymousContentKey::Type_ScrollbarButton;
if (aVertical) {
aKey |= AnonymousContentKey::Flag_Vertical;
}
if (aBottom) {
aKey |= AnonymousContentKey::Flag_ScrollbarButton_Bottom;
}
if (aDown) {
aKey |= AnonymousContentKey::Flag_ScrollbarButton_Down;
}
RefPtr<Element> e;
NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
e->SetAttr(kNameSpaceID_None, nsGkAtoms::sbattr,
kSbattrValues[aDown][aBottom], false);
e->SetAttr(kNameSpaceID_None, nsGkAtoms::type, kTypeValues[aDown], false);
return e.forget();
}
nsresult nsScrollbarFrame::CreateAnonymousContent(
nsTArray<ContentInfo>& aElements) {
nsNodeInfoManager* nodeInfoManager = mContent->NodeInfo()->NodeInfoManager();
Element* el(GetContent()->AsElement());
// If there are children already in the node, don't create any anonymous
// content (this only apply to crashtests/369038-1.xhtml)
if (el->HasChildren()) {
return NS_OK;
}
nsAutoString orient;
el->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient);
bool vertical = orient.EqualsLiteral("vertical");
RefPtr<dom::NodeInfo> sbbNodeInfo =
nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbarbutton, nullptr,
kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
bool createButtons = PresContext()->Theme()->ThemeSupportsScrollbarButtons();
if (createButtons) {
AnonymousContentKey key;
mUpTopButton =
MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
/* aDown */ false, key);
aElements.AppendElement(ContentInfo(mUpTopButton, key));
}
if (createButtons) {
AnonymousContentKey key;
mDownTopButton =
MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
/* aDown */ true, key);
aElements.AppendElement(ContentInfo(mDownTopButton, key));
}
{
AnonymousContentKey key = AnonymousContentKey::Type_Slider;
if (vertical) {
key |= AnonymousContentKey::Flag_Vertical;
}
NS_TrustedNewXULElement(
getter_AddRefs(mSlider),
nodeInfoManager->GetNodeInfo(nsGkAtoms::slider, nullptr,
kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
mSlider->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false);
aElements.AppendElement(ContentInfo(mSlider, key));
NS_TrustedNewXULElement(
getter_AddRefs(mThumb),
nodeInfoManager->GetNodeInfo(nsGkAtoms::thumb, nullptr,
kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false);
mSlider->AppendChildTo(mThumb, false, IgnoreErrors());
}
if (createButtons) {
AnonymousContentKey key;
mUpBottomButton =
MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
/* aDown */ false, key);
aElements.AppendElement(ContentInfo(mUpBottomButton, key));
}
if (createButtons) {
AnonymousContentKey key;
mDownBottomButton =
MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
/* aDown */ true, key);
aElements.AppendElement(ContentInfo(mDownBottomButton, key));
}
// Don't cache styles if we are inside a <select> element, since we have
// some UA style sheet rules that depend on the <select>'s attributes.
if (GetContent()->GetParent() &&
GetContent()->GetParent()->IsHTMLElement(nsGkAtoms::select)) {
for (auto& info : aElements) {
info.mKey = AnonymousContentKey::None;
}
}
UpdateChildrenAttributeValue(nsGkAtoms::curpos, false);
UpdateChildrenAttributeValue(nsGkAtoms::maxpos, false);
UpdateChildrenAttributeValue(nsGkAtoms::disabled, false);
UpdateChildrenAttributeValue(nsGkAtoms::pageincrement, false);
UpdateChildrenAttributeValue(nsGkAtoms::increment, false);
return NS_OK;
}
void nsScrollbarFrame::UpdateChildrenAttributeValue(nsAtom* aAttribute,
bool aNotify) {
Element* el(GetContent()->AsElement());
nsAutoString value;
el->GetAttr(kNameSpaceID_None, aAttribute, value);
if (!el->HasAttr(kNameSpaceID_None, aAttribute)) {
if (mUpTopButton) {
mUpTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
}
if (mDownTopButton) {
mDownTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
}
if (mSlider) {
mSlider->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
}
if (mThumb && aAttribute == nsGkAtoms::disabled) {
mThumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, aNotify);
}
if (mUpBottomButton) {
mUpBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
}
if (mDownBottomButton) {
mDownBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
}
return;
}
if (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos) {
if (mUpTopButton) {
mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mDownTopButton) {
mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mSlider) {
mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mUpBottomButton) {
mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mDownBottomButton) {
mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
} else if (aAttribute == nsGkAtoms::disabled) {
if (mUpTopButton) {
mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mDownTopButton) {
mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mSlider) {
mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
// Set the value on "collapsed" attribute.
if (mThumb) {
mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, value, aNotify);
}
if (mUpBottomButton) {
mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
if (mDownBottomButton) {
mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
} else if (aAttribute == nsGkAtoms::pageincrement ||
aAttribute == nsGkAtoms::increment) {
if (mSlider) {
mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
}
}
}
void nsScrollbarFrame::AppendAnonymousContentTo(
nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
if (mUpTopButton) {
aElements.AppendElement(mUpTopButton);
}
if (mDownTopButton) {
aElements.AppendElement(mDownTopButton);
}
if (mSlider) {
aElements.AppendElement(mSlider);
}
if (mUpBottomButton) {
aElements.AppendElement(mUpBottomButton);
}
if (mDownBottomButton) {
aElements.AppendElement(mDownBottomButton);
}
}