summaryrefslogtreecommitdiffstats
path: root/layout/xul/nsSplitterFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul/nsSplitterFrame.cpp')
-rw-r--r--layout/xul/nsSplitterFrame.cpp964
1 files changed, 964 insertions, 0 deletions
diff --git a/layout/xul/nsSplitterFrame.cpp b/layout/xul/nsSplitterFrame.cpp
new file mode 100644
index 0000000000..89d3ac1c25
--- /dev/null
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -0,0 +1,964 @@
+/* -*- 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 "LayoutConstants.h"
+#include "SimpleXULLeafFrame.h"
+#include "gfxContext.h"
+#include "mozilla/ReflowInput.h"
+#include "nsSplitterFrame.h"
+#include "nsGkAtoms.h"
+#include "nsXULElement.h"
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+#include "nsNameSpaceManager.h"
+#include "nsScrollbarButtonFrame.h"
+#include "nsIDOMEventListener.h"
+#include "nsICSSDeclaration.h"
+#include "nsFrameList.h"
+#include "nsHTMLParts.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/CSSOrderAwareFrameIterator.h"
+#include "nsContainerFrame.h"
+#include "nsContentCID.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsContentUtils.h"
+#include "nsFlexContainerFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/UniquePtr.h"
+#include "nsStyledElement.h"
+
+using namespace mozilla;
+
+using mozilla::dom::Element;
+using mozilla::dom::Event;
+
+class nsSplitterInfo {
+ public:
+ nscoord min;
+ nscoord max;
+ nscoord current;
+ nscoord pref;
+ nscoord changed;
+ nsCOMPtr<nsIContent> childElem;
+};
+
+enum class ResizeType {
+ // Resize the closest sibling in a given direction.
+ Closest,
+ // Resize the farthest sibling in a given direction.
+ Farthest,
+ // Resize only flexible siblings in a given direction.
+ Flex,
+ // No space should be taken out of any children in that direction.
+ // FIXME(emilio): This is a rather odd name...
+ Grow,
+ // Only resize adjacent siblings.
+ Sibling,
+ // Don't resize anything in a given direction.
+ None,
+};
+static ResizeType ResizeTypeFromAttribute(const Element& aElement,
+ nsAtom* aAttribute) {
+ static Element::AttrValuesArray strings[] = {
+ nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow,
+ nsGkAtoms::sibling, nsGkAtoms::none, nullptr};
+ switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings,
+ eCaseMatters)) {
+ case 0:
+ return ResizeType::Farthest;
+ case 1:
+ return ResizeType::Flex;
+ case 2:
+ // Grow only applies to resizeAfter.
+ if (aAttribute == nsGkAtoms::resizeafter) {
+ return ResizeType::Grow;
+ }
+ break;
+ case 3:
+ return ResizeType::Sibling;
+ case 4:
+ return ResizeType::None;
+ default:
+ break;
+ }
+ return ResizeType::Closest;
+}
+
+class nsSplitterFrameInner final : public nsIDOMEventListener {
+ protected:
+ virtual ~nsSplitterFrameInner();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
+ : mOuter(aSplitter) {}
+
+ void Disconnect() { mOuter = nullptr; }
+
+ nsresult MouseDown(Event* aMouseEvent);
+ nsresult MouseUp(Event* aMouseEvent);
+ nsresult MouseMove(Event* aMouseEvent);
+
+ void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
+ void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
+
+ void AdjustChildren(nsPresContext* aPresContext);
+ void AdjustChildren(nsPresContext* aPresContext,
+ nsTArray<nsSplitterInfo>& aChildInfos,
+ bool aIsHorizontal);
+
+ void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
+ int32_t& aSpaceLeft);
+
+ void ResizeChildTo(nscoord& aDiff);
+
+ void UpdateState();
+
+ void AddListener();
+ void RemoveListener();
+
+ enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
+ enum CollapseDirection { Before, After };
+
+ ResizeType GetResizeBefore();
+ ResizeType GetResizeAfter();
+ State GetState();
+
+ bool SupportsCollapseDirection(CollapseDirection aDirection);
+
+ void EnsureOrient();
+ void SetPreferredSize(nsIFrame* aChildBox, bool aIsHorizontal, nscoord aSize);
+
+ nsSplitterFrame* mOuter;
+ bool mDidDrag = false;
+ nscoord mDragStart = 0;
+ nsIFrame* mParentBox = nullptr;
+ bool mPressed = false;
+ nsTArray<nsSplitterInfo> mChildInfosBefore;
+ nsTArray<nsSplitterInfo> mChildInfosAfter;
+ State mState = State::Open;
+ nscoord mSplitterPos = 0;
+ bool mDragging = false;
+
+ const Element* SplitterElement() const {
+ return mOuter->GetContent()->AsElement();
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
+
+ResizeType nsSplitterFrameInner::GetResizeBefore() {
+ return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore);
+}
+
+ResizeType nsSplitterFrameInner::GetResizeAfter() {
+ return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter);
+}
+
+nsSplitterFrameInner::~nsSplitterFrameInner() = default;
+
+nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
+ nsGkAtoms::collapsed, nullptr};
+ static Element::AttrValuesArray strings_substate[] = {
+ nsGkAtoms::before, nsGkAtoms::after, nullptr};
+ switch (SplitterElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
+ case 0:
+ return State::Dragging;
+ case 1:
+ switch (SplitterElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
+ eCaseMatters)) {
+ case 0:
+ return State::CollapsedBefore;
+ case 1:
+ return State::CollapsedAfter;
+ default:
+ if (SupportsCollapseDirection(After)) {
+ return State::CollapsedAfter;
+ }
+ return State::CollapsedBefore;
+ }
+ }
+ return State::Open;
+}
+
+//
+// NS_NewSplitterFrame
+//
+// Creates a new Toolbar frame and returns it
+//
+nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
+
+nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SimpleXULLeafFrame(aStyle, aPresContext, kClassID) {}
+
+void nsSplitterFrame::Destroy(DestroyContext& aContext) {
+ if (mInner) {
+ mInner->RemoveListener();
+ mInner->Disconnect();
+ mInner = nullptr;
+ }
+ SimpleXULLeafFrame::Destroy(aContext);
+}
+
+nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ nsresult rv =
+ SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::state) {
+ mInner->UpdateState();
+ }
+
+ return rv;
+}
+
+/**
+ * Initialize us. If we are in a box get our alignment so we know what direction
+ * we are
+ */
+void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(!mInner);
+ mInner = new nsSplitterFrameInner(this);
+
+ SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mInner->AddListener();
+ mInner->mParentBox = nullptr;
+}
+
+static bool IsValidParentBox(nsIFrame* aFrame) {
+ return aFrame->IsFlexContainerFrame();
+}
+
+static nsIFrame* GetValidParentBox(nsIFrame* aChild) {
+ return aChild->GetParent() && IsValidParentBox(aChild->GetParent())
+ ? aChild->GetParent()
+ : nullptr;
+}
+
+void nsSplitterFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ mInner->mParentBox = GetValidParentBox(this);
+ mInner->UpdateState();
+ }
+ return SimpleXULLeafFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
+ aStatus);
+}
+
+static bool SplitterIsHorizontal(const nsIFrame* aParentBox) {
+ // If our parent is horizontal, the splitter is vertical and vice-versa.
+ MOZ_ASSERT(aParentBox->IsFlexContainerFrame());
+ const FlexboxAxisInfo info(aParentBox);
+ return !info.mIsRowOriented;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aControlHeld) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ return NS_OK;
+}
+
+void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists);
+
+ // if the mouse is captured always return us as the frame.
+ if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
+ // XXX It's probably better not to check visibility here, right?
+ aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
+ return;
+ }
+}
+
+nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ AutoWeakFrame weakFrame(this);
+ RefPtr<nsSplitterFrameInner> inner(mInner);
+ switch (aEvent->mMessage) {
+ case eMouseMove:
+ inner->MouseDrag(aPresContext, aEvent);
+ break;
+
+ case eMouseUp:
+ if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
+ inner->MouseUp(aPresContext, aEvent);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ return SimpleXULLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent) {
+ if (mDragging && mOuter) {
+ AdjustChildren(aPresContext);
+ AddListener();
+ PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
+ mDragging = false;
+ State newState = GetState();
+ // if the state is dragging then make it Open.
+ if (newState == State::Dragging) {
+ mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::state, u""_ns, true);
+ }
+
+ mPressed = false;
+
+ // if we dragged then fire a command event.
+ if (mDidDrag) {
+ RefPtr<nsXULElement> element =
+ nsXULElement::FromNode(mOuter->GetContent());
+ element->DoCommand();
+ }
+
+ // printf("MouseUp\n");
+ }
+
+ mChildInfosBefore.Clear();
+ mChildInfosAfter.Clear();
+}
+
+void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent) {
+ if (!mDragging || !mOuter) {
+ return;
+ }
+
+ const bool isHorizontal = !mOuter->IsHorizontal();
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, RelativeTo{mParentBox});
+ nscoord pos = isHorizontal ? pt.x : pt.y;
+
+ // take our current position and subtract the start location,
+ // mDragStart is in parent-box relative coordinates already.
+ pos -= mDragStart;
+
+ for (auto& info : mChildInfosBefore) {
+ info.changed = info.current;
+ }
+
+ for (auto& info : mChildInfosAfter) {
+ info.changed = info.current;
+ }
+ nscoord oldPos = pos;
+
+ ResizeChildTo(pos);
+
+ State currentState = GetState();
+ bool supportsBefore = SupportsCollapseDirection(Before);
+ bool supportsAfter = SupportsCollapseDirection(After);
+
+ const bool isRTL =
+ mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
+ bool pastEnd = oldPos > 0 && oldPos > pos;
+ bool pastBegin = oldPos < 0 && oldPos < pos;
+ if (isRTL) {
+ // Swap the boundary checks in RTL mode
+ std::swap(pastEnd, pastBegin);
+ }
+ const bool isCollapsedBefore = pastBegin && supportsBefore;
+ const bool isCollapsedAfter = pastEnd && supportsAfter;
+
+ // if we are in a collapsed position
+ if (isCollapsedBefore || isCollapsedAfter) {
+ // and we are not collapsed then collapse
+ if (currentState == State::Dragging) {
+ if (pastEnd) {
+ // printf("Collapse right\n");
+ if (supportsAfter) {
+ RefPtr<Element> outer = mOuter->mContent->AsElement();
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
+ true);
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
+ true);
+ }
+
+ } else if (pastBegin) {
+ // printf("Collapse left\n");
+ if (supportsBefore) {
+ RefPtr<Element> outer = mOuter->mContent->AsElement();
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
+ true);
+ outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
+ true);
+ }
+ }
+ }
+ } else {
+ // if we are not in a collapsed position and we are not dragging make sure
+ // we are dragging.
+ if (currentState != State::Dragging) {
+ mOuter->mContent->AsElement()->SetAttr(
+ kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
+ }
+ AdjustChildren(aPresContext);
+ }
+
+ mDidDrag = true;
+}
+
+void nsSplitterFrameInner::AddListener() {
+ mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false);
+ mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false);
+ mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false);
+ mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false);
+}
+
+void nsSplitterFrameInner::RemoveListener() {
+ NS_ENSURE_TRUE_VOID(mOuter);
+ mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false);
+ mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false);
+ mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false);
+ mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false);
+}
+
+nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
+ if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
+ if (eventType.EqualsLiteral("mousemove") ||
+ eventType.EqualsLiteral("mouseout"))
+ return MouseMove(aEvent);
+
+ MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
+ return NS_OK;
+}
+
+nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ mPressed = false;
+
+ PresShell::ReleaseCapturingContent();
+
+ return NS_OK;
+}
+
+template <typename LengthLike>
+static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
+ nscoord aFallback) {
+ if (aLengthLike.ConvertsToLength()) {
+ return aLengthLike.ToLength();
+ }
+ return aFallback;
+}
+
+template <typename LengthLike>
+static nsSize ToLengthWithFallback(const LengthLike& aWidth,
+ const LengthLike& aHeight,
+ nscoord aFallback = 0) {
+ return {ToLengthWithFallback(aWidth, aFallback),
+ ToLengthWithFallback(aHeight, aFallback)};
+}
+
+static void ApplyMargin(nsSize& aSize, const nsMargin& aMargin) {
+ if (aSize.width != NS_UNCONSTRAINEDSIZE) {
+ aSize.width += aMargin.LeftRight();
+ }
+ if (aSize.height != NS_UNCONSTRAINEDSIZE) {
+ aSize.height += aMargin.TopBottom();
+ }
+}
+
+nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_OK;
+ }
+
+ // only if left button
+ if (mouseEvent->Button() != 0) {
+ return NS_OK;
+ }
+
+ if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ return NS_OK;
+
+ mParentBox = GetValidParentBox(mOuter);
+ if (!mParentBox) {
+ return NS_OK;
+ }
+
+ // get our index
+ mDidDrag = false;
+
+ EnsureOrient();
+ const bool isHorizontal = !mOuter->IsHorizontal();
+
+ const nsIContent* outerContent = mOuter->GetContent();
+
+ const ResizeType resizeBefore = GetResizeBefore();
+ const ResizeType resizeAfter = GetResizeAfter();
+ const int32_t childCount = mParentBox->PrincipalChildList().GetLength();
+
+ mChildInfosBefore.Clear();
+ mChildInfosAfter.Clear();
+ int32_t count = 0;
+
+ bool foundOuter = false;
+ CSSOrderAwareFrameIterator iter(
+ mParentBox, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
+ CSSOrderAwareFrameIterator::OrderState::Unknown,
+ CSSOrderAwareFrameIterator::OrderingProperty::Order);
+ for (; !iter.AtEnd(); iter.Next()) {
+ nsIFrame* childBox = iter.get();
+ if (childBox == mOuter) {
+ foundOuter = true;
+ if (!count) {
+ // We're at the beginning, nothing to do.
+ return NS_OK;
+ }
+ if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
+ // If it's the last index then we need to allow for resizeafter="grow"
+ return NS_OK;
+ }
+ }
+ count++;
+
+ nsIContent* content = childBox->GetContent();
+ // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
+ // flex.
+ const nscoord flex = childBox->StyleXUL()->mBoxFlex;
+ const bool isBefore = !foundOuter;
+ const bool isResizable = [&] {
+ if (auto* element = nsXULElement::FromNode(content)) {
+ if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) {
+ // skip over any splitters
+ return false;
+ }
+
+ // We need to check for hidden attribute too, since treecols with
+ // the hidden="true" attribute are not really hidden, just collapsed
+ if (element->GetXULBoolAttr(nsGkAtoms::fixed) ||
+ element->GetXULBoolAttr(nsGkAtoms::hidden)) {
+ return false;
+ }
+ }
+
+ // We need to check this here rather than in the switch before because we
+ // want `sibling` to work in the DOM order, not frame tree order.
+ if (resizeBefore == ResizeType::Sibling &&
+ content->GetNextElementSibling() == outerContent) {
+ return true;
+ }
+ if (resizeAfter == ResizeType::Sibling &&
+ content->GetPreviousElementSibling() == outerContent) {
+ return true;
+ }
+
+ const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
+ switch (resizeType) {
+ case ResizeType::Grow:
+ case ResizeType::None:
+ case ResizeType::Sibling:
+ return false;
+ case ResizeType::Flex:
+ return flex > 0;
+ case ResizeType::Closest:
+ case ResizeType::Farthest:
+ break;
+ }
+ return true;
+ }();
+
+ if (!isResizable) {
+ continue;
+ }
+
+ nsSize curSize = childBox->GetSize();
+ const auto& pos = *childBox->StylePosition();
+ nsSize minSize = ToLengthWithFallback(pos.mMinWidth, pos.mMinHeight);
+ nsSize maxSize = ToLengthWithFallback(pos.mMaxWidth, pos.mMaxHeight,
+ NS_UNCONSTRAINEDSIZE);
+ nsSize prefSize(ToLengthWithFallback(pos.mWidth, curSize.width),
+ ToLengthWithFallback(pos.mHeight, curSize.height));
+
+ maxSize.width = std::max(maxSize.width, minSize.width);
+ maxSize.height = std::max(maxSize.height, minSize.height);
+ prefSize.width =
+ NS_CSS_MINMAX(prefSize.width, minSize.width, maxSize.width);
+ prefSize.height =
+ NS_CSS_MINMAX(prefSize.height, minSize.height, maxSize.height);
+
+ nsMargin m;
+ childBox->StyleMargin()->GetMargin(m);
+
+ ApplyMargin(curSize, m);
+ ApplyMargin(minSize, m);
+ ApplyMargin(maxSize, m);
+ ApplyMargin(prefSize, m);
+
+ auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter;
+ nsSplitterInfo& info = *list.AppendElement();
+ info.childElem = content;
+ info.min = isHorizontal ? minSize.width : minSize.height;
+ info.max = isHorizontal ? maxSize.width : maxSize.height;
+ info.pref = isHorizontal ? prefSize.width : prefSize.height;
+ info.current = info.changed = isHorizontal ? curSize.width : curSize.height;
+ }
+
+ if (!foundOuter) {
+ return NS_OK;
+ }
+
+ mPressed = true;
+
+ const bool reverseDirection = [&] {
+ MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
+ const FlexboxAxisInfo info(mParentBox);
+ if (!info.mIsRowOriented) {
+ return info.mIsMainAxisReversed;
+ }
+ const bool rtl =
+ mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
+ return info.mIsMainAxisReversed != rtl;
+ }();
+
+ if (reverseDirection) {
+ // The before array is really the after array, and the order needs to be
+ // reversed. First reverse both arrays.
+ mChildInfosBefore.Reverse();
+ mChildInfosAfter.Reverse();
+
+ // Now swap the two arrays.
+ std::swap(mChildInfosBefore, mChildInfosAfter);
+ }
+
+ // if resizebefore is not Farthest, reverse the list because the first child
+ // in the list is the farthest, and we want the first child to be the closest.
+ if (resizeBefore != ResizeType::Farthest) {
+ mChildInfosBefore.Reverse();
+ }
+
+ // if the resizeafter is the Farthest we must reverse the list because the
+ // first child in the list is the closest we want the first child to be the
+ // Farthest.
+ if (resizeAfter == ResizeType::Farthest) {
+ mChildInfosAfter.Reverse();
+ }
+
+ int32_t c;
+ nsPoint pt =
+ nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
+ if (isHorizontal) {
+ c = pt.x;
+ mSplitterPos = mOuter->mRect.x;
+ } else {
+ c = pt.y;
+ mSplitterPos = mOuter->mRect.y;
+ }
+
+ mDragStart = c;
+
+ // printf("Pressed mDragStart=%d\n",mDragStart);
+
+ PresShell::SetCapturingContent(mOuter->GetContent(),
+ CaptureFlags::IgnoreAllowedState);
+
+ return NS_OK;
+}
+
+nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
+ NS_ENSURE_TRUE(mOuter, NS_OK);
+ if (!mPressed) {
+ return NS_OK;
+ }
+
+ if (mDragging) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
+ mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
+ u"dragging"_ns, true);
+
+ RemoveListener();
+ mDragging = true;
+
+ return NS_OK;
+}
+
+bool nsSplitterFrameInner::SupportsCollapseDirection(
+ nsSplitterFrameInner::CollapseDirection aDirection) {
+ static Element::AttrValuesArray strings[] = {
+ nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
+
+ switch (SplitterElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
+ case 0:
+ return (aDirection == Before);
+ case 1:
+ return (aDirection == After);
+ case 2:
+ return true;
+ }
+
+ return false;
+}
+
+static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
+ nsIFrame* parent = aBox->GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+ CSSOrderAwareFrameIterator iter(
+ parent, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
+ CSSOrderAwareFrameIterator::OrderState::Unknown,
+ CSSOrderAwareFrameIterator::OrderingProperty::Order);
+
+ nsIFrame* prevSibling = nullptr;
+ for (; !iter.AtEnd(); iter.Next()) {
+ nsIFrame* current = iter.get();
+ if (!aNext && current == aBox) {
+ return prevSibling;
+ }
+ if (aNext && prevSibling == aBox) {
+ return current;
+ }
+ prevSibling = current;
+ }
+ return nullptr;
+}
+
+void nsSplitterFrameInner::UpdateState() {
+ // State Transitions:
+ // Open -> Dragging
+ // Open -> CollapsedBefore
+ // Open -> CollapsedAfter
+ // CollapsedBefore -> Open
+ // CollapsedBefore -> Dragging
+ // CollapsedAfter -> Open
+ // CollapsedAfter -> Dragging
+ // Dragging -> Open
+ // Dragging -> CollapsedBefore (auto collapse)
+ // Dragging -> CollapsedAfter (auto collapse)
+
+ State newState = GetState();
+
+ if (newState == mState) {
+ // No change.
+ return;
+ }
+
+ if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
+ IsValidParentBox(mOuter->GetParent())) {
+ // Find the splitter's immediate sibling.
+ const bool prev =
+ newState == State::CollapsedBefore || mState == State::CollapsedBefore;
+ nsIFrame* splitterSibling = SlowOrderAwareSibling(mOuter, !prev);
+ if (splitterSibling) {
+ nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
+ if (sibling && sibling->IsElement()) {
+ if (mState == State::CollapsedBefore ||
+ mState == State::CollapsedAfter) {
+ // CollapsedBefore -> Open
+ // CollapsedBefore -> Dragging
+ // CollapsedAfter -> Open
+ // CollapsedAfter -> Dragging
+ nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
+ sibling->AsElement(), nsGkAtoms::collapsed));
+ } else if ((mState == State::Open || mState == State::Dragging) &&
+ (newState == State::CollapsedBefore ||
+ newState == State::CollapsedAfter)) {
+ // Open -> CollapsedBefore / CollapsedAfter
+ // Dragging -> CollapsedBefore / CollapsedAfter
+ nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
+ sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
+ }
+ }
+ }
+ }
+ mState = newState;
+}
+
+void nsSplitterFrameInner::EnsureOrient() {
+ mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
+}
+
+void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
+ EnsureOrient();
+ const bool isHorizontal = !mOuter->IsHorizontal();
+
+ AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal);
+ AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal);
+}
+
+static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
+ nsIContent* aContent) {
+ // XXX Can this use GetPrimaryFrame?
+ for (nsIFrame* f : aParentBox->PrincipalChildList()) {
+ if (f->GetContent() == aContent) {
+ return f;
+ }
+ }
+ return nullptr;
+}
+
+void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
+ nsTArray<nsSplitterInfo>& aChildInfos,
+ bool aIsHorizontal) {
+ /// printf("------- AdjustChildren------\n");
+
+ for (auto& info : aChildInfos) {
+ nscoord newPref = info.pref + (info.changed - info.current);
+ if (nsIFrame* childBox =
+ GetChildBoxForContent(mParentBox, info.childElem)) {
+ SetPreferredSize(childBox, aIsHorizontal, newPref);
+ }
+ }
+}
+
+void nsSplitterFrameInner::SetPreferredSize(nsIFrame* aChildBox,
+ bool aIsHorizontal, nscoord aSize) {
+ nsMargin margin;
+ aChildBox->StyleMargin()->GetMargin(margin);
+ if (aIsHorizontal) {
+ aSize -= (margin.left + margin.right);
+ } else {
+ aSize -= (margin.top + margin.bottom);
+ }
+
+ RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
+ if (!element) {
+ return;
+ }
+
+ // We set both the attribute and the CSS value, so that XUL persist="" keeps
+ // working, see bug 1790712.
+
+ int32_t pixels = aSize / AppUnitsPerCSSPixel();
+ nsAutoString attrValue;
+ attrValue.AppendInt(pixels);
+ element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height,
+ attrValue, IgnoreErrors());
+
+ nsCOMPtr<nsICSSDeclaration> decl = element->Style();
+
+ nsAutoCString cssValue;
+ cssValue.AppendInt(pixels);
+ cssValue.AppendLiteral("px");
+ decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns,
+ IgnoreErrors());
+}
+
+void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
+ nsTArray<nsSplitterInfo>& aChildInfos,
+ int32_t& aSpaceLeft) {
+ aSpaceLeft = 0;
+
+ for (auto& info : aChildInfos) {
+ nscoord min = info.min;
+ nscoord max = info.max;
+ nscoord& c = info.changed;
+
+ // figure our how much space to add or remove
+ if (c + aDiff < min) {
+ aDiff += (c - min);
+ c = min;
+ } else if (c + aDiff > max) {
+ aDiff -= (max - c);
+ c = max;
+ } else {
+ c += aDiff;
+ aDiff = 0;
+ }
+
+ // there is not space left? We are done
+ if (aDiff == 0) {
+ break;
+ }
+ }
+
+ aSpaceLeft = aDiff;
+}
+
+/**
+ * Ok if we want to resize a child we will know the actual size in pixels we
+ * want it to be. This is not the preferred size. But the only way we can change
+ * a child is by manipulating its preferred size. So give the actual pixel size
+ * this method will figure out the preferred size and set it.
+ */
+
+void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) {
+ nscoord spaceLeft = 0;
+
+ if (!mChildInfosBefore.IsEmpty()) {
+ AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft);
+ // If there is any space left over remove it from the diff we were
+ // originally given.
+ aDiff -= spaceLeft;
+ }
+
+ AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
+
+ if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
+ aDiff += spaceLeft;
+ AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);
+ }
+}