tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsSplitterFrame.cpp (30344B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 //
      8 // Eric Vaughan
      9 // Netscape Communications
     10 //
     11 // See documentation in associated header file
     12 //
     13 
     14 #include "nsSplitterFrame.h"
     15 
     16 #include "LayoutConstants.h"
     17 #include "SimpleXULLeafFrame.h"
     18 #include "gfxContext.h"
     19 #include "mozilla/CSSOrderAwareFrameIterator.h"
     20 #include "mozilla/ComputedStyle.h"
     21 #include "mozilla/MouseEvents.h"
     22 #include "mozilla/PresShell.h"
     23 #include "mozilla/ReflowInput.h"
     24 #include "mozilla/dom/Document.h"
     25 #include "mozilla/dom/Element.h"
     26 #include "mozilla/dom/Event.h"
     27 #include "mozilla/dom/MouseEvent.h"
     28 #include "nsContainerFrame.h"
     29 #include "nsContentUtils.h"
     30 #include "nsDOMCSSDeclaration.h"
     31 #include "nsDisplayList.h"
     32 #include "nsFlexContainerFrame.h"
     33 #include "nsFrameList.h"
     34 #include "nsGkAtoms.h"
     35 #include "nsHTMLParts.h"
     36 #include "nsIDOMEventListener.h"
     37 #include "nsLayoutUtils.h"
     38 #include "nsNameSpaceManager.h"
     39 #include "nsPresContext.h"
     40 #include "nsScrollbarButtonFrame.h"
     41 #include "nsStyledElement.h"
     42 #include "nsXULElement.h"
     43 
     44 using namespace mozilla;
     45 
     46 using mozilla::dom::Element;
     47 using mozilla::dom::Event;
     48 
     49 class nsSplitterInfo {
     50 public:
     51  nscoord min;
     52  nscoord max;
     53  nscoord current;
     54  nscoord pref;
     55  nscoord changed;
     56  nsCOMPtr<nsIContent> childElem;
     57 };
     58 
     59 enum class ResizeType {
     60  // Resize the closest sibling in a given direction.
     61  Closest,
     62  // Resize the farthest sibling in a given direction.
     63  Farthest,
     64  // Resize only flexible siblings in a given direction.
     65  Flex,
     66  // No space should be taken out of any children in that direction.
     67  // FIXME(emilio): This is a rather odd name...
     68  Grow,
     69  // Only resize adjacent siblings.
     70  Sibling,
     71  // Don't resize anything in a given direction.
     72  None,
     73 };
     74 static ResizeType ResizeTypeFromAttribute(const Element& aElement,
     75                                          nsAtom* aAttribute) {
     76  static Element::AttrValuesArray strings[] = {
     77      nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow,
     78      nsGkAtoms::sibling,  nsGkAtoms::none, nullptr};
     79  switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings,
     80                                   eCaseMatters)) {
     81    case 0:
     82      return ResizeType::Farthest;
     83    case 1:
     84      return ResizeType::Flex;
     85    case 2:
     86      // Grow only applies to resizeAfter.
     87      if (aAttribute == nsGkAtoms::resizeafter) {
     88        return ResizeType::Grow;
     89      }
     90      break;
     91    case 3:
     92      return ResizeType::Sibling;
     93    case 4:
     94      return ResizeType::None;
     95    default:
     96      break;
     97  }
     98  return ResizeType::Closest;
     99 }
    100 
    101 class nsSplitterFrameInner final : public nsIDOMEventListener {
    102 protected:
    103  virtual ~nsSplitterFrameInner();
    104 
    105 public:
    106  NS_DECL_ISUPPORTS
    107  NS_DECL_NSIDOMEVENTLISTENER
    108 
    109  explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
    110      : mOuter(aSplitter) {}
    111 
    112  void Disconnect() { mOuter = nullptr; }
    113 
    114  nsresult MouseDown(Event* aMouseEvent);
    115  nsresult MouseUp(Event* aMouseEvent);
    116  nsresult MouseMove(Event* aMouseEvent);
    117 
    118  void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
    119  void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
    120 
    121  void AdjustChildren(nsPresContext* aPresContext);
    122  void AdjustChildren(nsPresContext* aPresContext,
    123                      nsTArray<nsSplitterInfo>& aChildInfos,
    124                      bool aIsHorizontal);
    125 
    126  void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
    127                      int32_t& aSpaceLeft);
    128 
    129  void ResizeChildTo(nscoord& aDiff);
    130 
    131  void UpdateState();
    132 
    133  void AddListener();
    134  void RemoveListener();
    135 
    136  enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
    137  enum CollapseDirection { Before, After };
    138 
    139  ResizeType GetResizeBefore();
    140  ResizeType GetResizeAfter();
    141  State GetState();
    142 
    143  bool SupportsCollapseDirection(CollapseDirection aDirection);
    144 
    145  void EnsureOrient();
    146  void SetPreferredSize(nsIFrame* aChildBox, bool aIsHorizontal, nscoord aSize);
    147 
    148  nsSplitterFrame* mOuter;
    149  bool mDidDrag = false;
    150  nscoord mDragStart = 0;
    151  nsIFrame* mParentBox = nullptr;
    152  bool mPressed = false;
    153  nsTArray<nsSplitterInfo> mChildInfosBefore;
    154  nsTArray<nsSplitterInfo> mChildInfosAfter;
    155  State mState = State::Open;
    156  nscoord mSplitterPos = 0;
    157  bool mDragging = false;
    158 
    159  const Element* SplitterElement() const {
    160    return mOuter->GetContent()->AsElement();
    161  }
    162 };
    163 
    164 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
    165 
    166 ResizeType nsSplitterFrameInner::GetResizeBefore() {
    167  return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore);
    168 }
    169 
    170 ResizeType nsSplitterFrameInner::GetResizeAfter() {
    171  return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter);
    172 }
    173 
    174 nsSplitterFrameInner::~nsSplitterFrameInner() = default;
    175 
    176 nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
    177  static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
    178                                               nsGkAtoms::collapsed, nullptr};
    179  static Element::AttrValuesArray strings_substate[] = {
    180      nsGkAtoms::before, nsGkAtoms::after, nullptr};
    181  switch (SplitterElement()->FindAttrValueIn(
    182      kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
    183    case 0:
    184      return State::Dragging;
    185    case 1:
    186      switch (SplitterElement()->FindAttrValueIn(
    187          kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
    188          eCaseMatters)) {
    189        case 0:
    190          return State::CollapsedBefore;
    191        case 1:
    192          return State::CollapsedAfter;
    193        default:
    194          if (SupportsCollapseDirection(After)) {
    195            return State::CollapsedAfter;
    196          }
    197          return State::CollapsedBefore;
    198      }
    199  }
    200  return State::Open;
    201 }
    202 
    203 //
    204 // NS_NewSplitterFrame
    205 //
    206 // Creates a new Toolbar frame and returns it
    207 //
    208 nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
    209  return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext());
    210 }
    211 
    212 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
    213 
    214 nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle,
    215                                 nsPresContext* aPresContext)
    216    : SimpleXULLeafFrame(aStyle, aPresContext, kClassID) {}
    217 
    218 void nsSplitterFrame::Destroy(DestroyContext& aContext) {
    219  if (mInner) {
    220    mInner->RemoveListener();
    221    mInner->Disconnect();
    222    mInner = nullptr;
    223  }
    224  SimpleXULLeafFrame::Destroy(aContext);
    225 }
    226 
    227 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
    228                                           nsAtom* aAttribute,
    229                                           AttrModType aModType) {
    230  nsresult rv =
    231      SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
    232  if (aAttribute == nsGkAtoms::state) {
    233    mInner->UpdateState();
    234  }
    235 
    236  return rv;
    237 }
    238 
    239 /**
    240 * Initialize us. If we are in a box get our alignment so we know what direction
    241 * we are
    242 */
    243 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
    244                           nsIFrame* aPrevInFlow) {
    245  MOZ_ASSERT(!mInner);
    246  mInner = new nsSplitterFrameInner(this);
    247 
    248  SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);
    249 
    250  mInner->AddListener();
    251  mInner->mParentBox = nullptr;
    252 }
    253 
    254 static bool IsValidParentBox(nsIFrame* aFrame) {
    255  return aFrame->IsFlexContainerFrame();
    256 }
    257 
    258 static nsIFrame* GetValidParentBox(nsIFrame* aChild) {
    259  return aChild->GetParent() && IsValidParentBox(aChild->GetParent())
    260             ? aChild->GetParent()
    261             : nullptr;
    262 }
    263 
    264 void nsSplitterFrame::Reflow(nsPresContext* aPresContext,
    265                             ReflowOutput& aDesiredSize,
    266                             const ReflowInput& aReflowInput,
    267                             nsReflowStatus& aStatus) {
    268  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    269    mInner->mParentBox = GetValidParentBox(this);
    270    mInner->UpdateState();
    271  }
    272  return SimpleXULLeafFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
    273                                    aStatus);
    274 }
    275 
    276 static bool SplitterIsHorizontal(const nsIFrame* aParentBox) {
    277  // If our parent is horizontal, the splitter is vertical and vice-versa.
    278  MOZ_ASSERT(aParentBox->IsFlexContainerFrame());
    279  const FlexboxAxisInfo info(aParentBox);
    280  return !info.mIsRowOriented;
    281 }
    282 
    283 NS_IMETHODIMP
    284 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
    285                             WidgetGUIEvent* aEvent,
    286                             nsEventStatus* aEventStatus) {
    287  return NS_OK;
    288 }
    289 
    290 NS_IMETHODIMP
    291 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
    292                                     WidgetGUIEvent* aEvent,
    293                                     nsEventStatus* aEventStatus,
    294                                     bool aControlHeld) {
    295  return NS_OK;
    296 }
    297 
    298 NS_IMETHODIMP
    299 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
    300                            nsEventStatus* aEventStatus) {
    301  return NS_OK;
    302 }
    303 
    304 NS_IMETHODIMP
    305 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
    306                               WidgetGUIEvent* aEvent,
    307                               nsEventStatus* aEventStatus) {
    308  return NS_OK;
    309 }
    310 
    311 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    312                                       const nsDisplayListSet& aLists) {
    313  SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists);
    314 
    315  // if the mouse is captured always return us as the frame.
    316  if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
    317    // XXX It's probably better not to check visibility here, right?
    318    aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
    319    return;
    320  }
    321 }
    322 
    323 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
    324                                      WidgetGUIEvent* aEvent,
    325                                      nsEventStatus* aEventStatus) {
    326  NS_ENSURE_ARG_POINTER(aEventStatus);
    327  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
    328    return NS_OK;
    329  }
    330 
    331  AutoWeakFrame weakFrame(this);
    332  RefPtr<nsSplitterFrameInner> inner(mInner);
    333  switch (aEvent->mMessage) {
    334    case eMouseMove:
    335      inner->MouseDrag(aPresContext, aEvent);
    336      break;
    337 
    338    case eMouseUp:
    339      if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
    340        inner->MouseUp(aPresContext, aEvent);
    341      }
    342      break;
    343 
    344    default:
    345      break;
    346  }
    347 
    348  NS_ENSURE_STATE(weakFrame.IsAlive());
    349  return SimpleXULLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
    350 }
    351 
    352 void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
    353                                   WidgetGUIEvent* aEvent) {
    354  if (mDragging && mOuter) {
    355    AdjustChildren(aPresContext);
    356    AddListener();
    357    PresShell::ReleaseCapturingContent();  // XXXndeakin is this needed?
    358    mDragging = false;
    359    State newState = GetState();
    360    // if the state is dragging then make it Open.
    361    if (newState == State::Dragging) {
    362      mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
    363                                             nsGkAtoms::state, u""_ns, true);
    364    }
    365 
    366    mPressed = false;
    367 
    368    // if we dragged then fire a command event.
    369    if (mDidDrag) {
    370      RefPtr<nsXULElement> element =
    371          nsXULElement::FromNode(mOuter->GetContent());
    372      element->DoCommand();
    373    }
    374 
    375    // printf("MouseUp\n");
    376  }
    377 
    378  mChildInfosBefore.Clear();
    379  mChildInfosAfter.Clear();
    380 }
    381 
    382 void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
    383                                     WidgetGUIEvent* aEvent) {
    384  if (!mDragging || !mOuter) {
    385    return;
    386  }
    387 
    388  const bool isHorizontal = !mOuter->IsHorizontal();
    389  nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
    390      aEvent, RelativeTo{mParentBox});
    391  nscoord pos = isHorizontal ? pt.x : pt.y;
    392 
    393  // take our current position and subtract the start location,
    394  // mDragStart is in parent-box relative coordinates already.
    395  pos -= mDragStart;
    396 
    397  for (auto& info : mChildInfosBefore) {
    398    info.changed = info.current;
    399  }
    400 
    401  for (auto& info : mChildInfosAfter) {
    402    info.changed = info.current;
    403  }
    404  nscoord oldPos = pos;
    405 
    406  ResizeChildTo(pos);
    407 
    408  State currentState = GetState();
    409  bool supportsBefore = SupportsCollapseDirection(Before);
    410  bool supportsAfter = SupportsCollapseDirection(After);
    411 
    412  const bool isRTL =
    413      mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
    414  bool pastEnd = oldPos > 0 && oldPos > pos;
    415  bool pastBegin = oldPos < 0 && oldPos < pos;
    416  if (isRTL) {
    417    // Swap the boundary checks in RTL mode
    418    std::swap(pastEnd, pastBegin);
    419  }
    420  const bool isCollapsedBefore = pastBegin && supportsBefore;
    421  const bool isCollapsedAfter = pastEnd && supportsAfter;
    422 
    423  // if we are in a collapsed position
    424  if (isCollapsedBefore || isCollapsedAfter) {
    425    // and we are not collapsed then collapse
    426    if (currentState == State::Dragging) {
    427      if (pastEnd) {
    428        // printf("Collapse right\n");
    429        if (supportsAfter) {
    430          RefPtr<Element> outer = mOuter->mContent->AsElement();
    431          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
    432                         true);
    433          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
    434                         true);
    435        }
    436 
    437      } else if (pastBegin) {
    438        // printf("Collapse left\n");
    439        if (supportsBefore) {
    440          RefPtr<Element> outer = mOuter->mContent->AsElement();
    441          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
    442                         true);
    443          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
    444                         true);
    445        }
    446      }
    447    }
    448  } else {
    449    // if we are not in a collapsed position and we are not dragging make sure
    450    // we are dragging.
    451    if (currentState != State::Dragging) {
    452      mOuter->mContent->AsElement()->SetAttr(
    453          kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
    454    }
    455    AdjustChildren(aPresContext);
    456  }
    457 
    458  mDidDrag = true;
    459 }
    460 
    461 void nsSplitterFrameInner::AddListener() {
    462  mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false);
    463  mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false);
    464  mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false);
    465  mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false);
    466 }
    467 
    468 void nsSplitterFrameInner::RemoveListener() {
    469  NS_ENSURE_TRUE_VOID(mOuter);
    470  mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false);
    471  mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false);
    472  mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false);
    473  mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false);
    474 }
    475 
    476 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
    477  nsAutoString eventType;
    478  aEvent->GetType(eventType);
    479  if (eventType.EqualsLiteral("mouseup")) {
    480    return MouseUp(aEvent);
    481  }
    482  if (eventType.EqualsLiteral("mousedown")) {
    483    return MouseDown(aEvent);
    484  }
    485  if (eventType.EqualsLiteral("mousemove") ||
    486      eventType.EqualsLiteral("mouseout")) {
    487    return MouseMove(aEvent);
    488  }
    489 
    490  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
    491  return NS_OK;
    492 }
    493 
    494 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
    495  NS_ENSURE_TRUE(mOuter, NS_OK);
    496  mPressed = false;
    497 
    498  PresShell::ReleaseCapturingContent();
    499 
    500  return NS_OK;
    501 }
    502 
    503 template <typename LengthLike>
    504 static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
    505                                    nscoord aFallback) {
    506  if (aLengthLike.ConvertsToLength()) {
    507    return aLengthLike.ToLength();
    508  }
    509  return aFallback;
    510 }
    511 
    512 template <typename LengthLike>
    513 static nsSize ToLengthWithFallback(const LengthLike& aWidth,
    514                                   const LengthLike& aHeight,
    515                                   nscoord aFallback = 0) {
    516  return {ToLengthWithFallback(aWidth, aFallback),
    517          ToLengthWithFallback(aHeight, aFallback)};
    518 }
    519 
    520 static void ApplyMargin(nsSize& aSize, const nsMargin& aMargin) {
    521  if (aSize.width != NS_UNCONSTRAINEDSIZE) {
    522    aSize.width += aMargin.LeftRight();
    523  }
    524  if (aSize.height != NS_UNCONSTRAINEDSIZE) {
    525    aSize.height += aMargin.TopBottom();
    526  }
    527 }
    528 
    529 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
    530  NS_ENSURE_TRUE(mOuter, NS_OK);
    531  dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
    532  if (!mouseEvent) {
    533    return NS_OK;
    534  }
    535 
    536  // only if left button
    537  if (mouseEvent->Button() != 0) {
    538    return NS_OK;
    539  }
    540 
    541  if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
    542                                     nsGkAtoms::_true, eCaseMatters)) {
    543    return NS_OK;
    544  }
    545 
    546  mParentBox = GetValidParentBox(mOuter);
    547  if (!mParentBox) {
    548    return NS_OK;
    549  }
    550 
    551  // get our index
    552  mDidDrag = false;
    553 
    554  EnsureOrient();
    555  const bool isHorizontal = !mOuter->IsHorizontal();
    556 
    557  const nsIContent* outerContent = mOuter->GetContent();
    558 
    559  const ResizeType resizeBefore = GetResizeBefore();
    560  const ResizeType resizeAfter = GetResizeAfter();
    561  const int32_t childCount = mParentBox->PrincipalChildList().GetLength();
    562 
    563  mChildInfosBefore.Clear();
    564  mChildInfosAfter.Clear();
    565  int32_t count = 0;
    566 
    567  bool foundOuter = false;
    568  CSSOrderAwareFrameIterator iter(
    569      mParentBox, FrameChildListID::Principal,
    570      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
    571      CSSOrderAwareFrameIterator::OrderState::Unknown,
    572      CSSOrderAwareFrameIterator::OrderingProperty::Order);
    573  for (; !iter.AtEnd(); iter.Next()) {
    574    nsIFrame* childBox = iter.get();
    575    if (childBox == mOuter) {
    576      foundOuter = true;
    577      if (!count) {
    578        // We're at the beginning, nothing to do.
    579        return NS_OK;
    580      }
    581      if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
    582        // If it's the last index then we need to allow for resizeafter="grow"
    583        return NS_OK;
    584      }
    585    }
    586    count++;
    587 
    588    nsIContent* content = childBox->GetContent();
    589    // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
    590    // flex.
    591    const nscoord flex = childBox->StyleXUL()->mBoxFlex;
    592    const bool isBefore = !foundOuter;
    593    const bool isResizable = [&] {
    594      if (auto* element = nsXULElement::FromNode(content)) {
    595        if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) {
    596          // skip over any splitters
    597          return false;
    598        }
    599 
    600        // We need to check for hidden attribute too, since treecols with
    601        // the hidden attribute are not really hidden, just collapsed
    602        if (element->GetXULBoolAttr(nsGkAtoms::fixed) ||
    603            element->GetBoolAttr(nsGkAtoms::hidden)) {
    604          return false;
    605        }
    606      }
    607 
    608      // We need to check this here rather than in the switch before because we
    609      // want `sibling` to work in the DOM order, not frame tree order.
    610      if (resizeBefore == ResizeType::Sibling &&
    611          content->GetNextElementSibling() == outerContent) {
    612        return true;
    613      }
    614      if (resizeAfter == ResizeType::Sibling &&
    615          content->GetPreviousElementSibling() == outerContent) {
    616        return true;
    617      }
    618 
    619      const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
    620      switch (resizeType) {
    621        case ResizeType::Grow:
    622        case ResizeType::None:
    623        case ResizeType::Sibling:
    624          return false;
    625        case ResizeType::Flex:
    626          return flex > 0;
    627        case ResizeType::Closest:
    628        case ResizeType::Farthest:
    629          break;
    630      }
    631      return true;
    632    }();
    633 
    634    if (!isResizable) {
    635      continue;
    636    }
    637 
    638    nsSize curSize = childBox->GetSize();
    639    const auto& pos = *childBox->StylePosition();
    640    const auto anchorResolutionParams =
    641        AnchorPosResolutionParams::From(childBox);
    642    nsSize minSize =
    643        ToLengthWithFallback(*pos.GetMinWidth(anchorResolutionParams),
    644                             *pos.GetMinHeight(anchorResolutionParams));
    645    nsSize maxSize = ToLengthWithFallback(
    646        *pos.GetMaxWidth(anchorResolutionParams),
    647        *pos.GetMaxHeight(anchorResolutionParams), NS_UNCONSTRAINEDSIZE);
    648    nsSize prefSize(ToLengthWithFallback(*pos.GetWidth(anchorResolutionParams),
    649                                         curSize.width),
    650                    ToLengthWithFallback(*pos.GetHeight(anchorResolutionParams),
    651                                         curSize.height));
    652 
    653    maxSize.width = std::max(maxSize.width, minSize.width);
    654    maxSize.height = std::max(maxSize.height, minSize.height);
    655    prefSize.width = CSSMinMax(prefSize.width, minSize.width, maxSize.width);
    656    prefSize.height =
    657        CSSMinMax(prefSize.height, minSize.height, maxSize.height);
    658 
    659    nsMargin m;
    660    childBox->StyleMargin()->GetMargin(m);
    661 
    662    ApplyMargin(curSize, m);
    663    ApplyMargin(minSize, m);
    664    ApplyMargin(maxSize, m);
    665    ApplyMargin(prefSize, m);
    666 
    667    auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter;
    668    nsSplitterInfo& info = *list.AppendElement();
    669    info.childElem = content;
    670    info.min = isHorizontal ? minSize.width : minSize.height;
    671    info.max = isHorizontal ? maxSize.width : maxSize.height;
    672    info.pref = isHorizontal ? prefSize.width : prefSize.height;
    673    info.current = info.changed = isHorizontal ? curSize.width : curSize.height;
    674  }
    675 
    676  if (!foundOuter) {
    677    return NS_OK;
    678  }
    679 
    680  mPressed = true;
    681 
    682  const bool reverseDirection = [&] {
    683    MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
    684    const FlexboxAxisInfo info(mParentBox);
    685    if (!info.mIsRowOriented) {
    686      return info.mIsMainAxisReversed;
    687    }
    688    const bool rtl =
    689        mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
    690    return info.mIsMainAxisReversed != rtl;
    691  }();
    692 
    693  if (reverseDirection) {
    694    // The before array is really the after array, and the order needs to be
    695    // reversed. First reverse both arrays.
    696    mChildInfosBefore.Reverse();
    697    mChildInfosAfter.Reverse();
    698 
    699    // Now swap the two arrays.
    700    std::swap(mChildInfosBefore, mChildInfosAfter);
    701  }
    702 
    703  // if resizebefore is not Farthest, reverse the list because the first child
    704  // in the list is the farthest, and we want the first child to be the closest.
    705  if (resizeBefore != ResizeType::Farthest) {
    706    mChildInfosBefore.Reverse();
    707  }
    708 
    709  // if the resizeafter is the Farthest we must reverse the list because the
    710  // first child in the list is the closest we want the first child to be the
    711  // Farthest.
    712  if (resizeAfter == ResizeType::Farthest) {
    713    mChildInfosAfter.Reverse();
    714  }
    715 
    716  int32_t c;
    717  nsPoint pt =
    718      nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
    719  if (isHorizontal) {
    720    c = pt.x;
    721    mSplitterPos = mOuter->mRect.x;
    722  } else {
    723    c = pt.y;
    724    mSplitterPos = mOuter->mRect.y;
    725  }
    726 
    727  mDragStart = c;
    728 
    729  // printf("Pressed mDragStart=%d\n",mDragStart);
    730 
    731  PresShell::SetCapturingContent(mOuter->GetContent(),
    732                                 CaptureFlags::IgnoreAllowedState);
    733 
    734  return NS_OK;
    735 }
    736 
    737 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
    738  NS_ENSURE_TRUE(mOuter, NS_OK);
    739  if (!mPressed) {
    740    return NS_OK;
    741  }
    742 
    743  if (mDragging) {
    744    return NS_OK;
    745  }
    746 
    747  nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
    748  mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
    749                                         u"dragging"_ns, true);
    750 
    751  RemoveListener();
    752  mDragging = true;
    753 
    754  return NS_OK;
    755 }
    756 
    757 bool nsSplitterFrameInner::SupportsCollapseDirection(
    758    nsSplitterFrameInner::CollapseDirection aDirection) {
    759  static Element::AttrValuesArray strings[] = {
    760      nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
    761 
    762  switch (SplitterElement()->FindAttrValueIn(
    763      kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
    764    case 0:
    765      return (aDirection == Before);
    766    case 1:
    767      return (aDirection == After);
    768    case 2:
    769      return true;
    770  }
    771 
    772  return false;
    773 }
    774 
    775 static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
    776  nsIFrame* parent = aBox->GetParent();
    777  if (!parent) {
    778    return nullptr;
    779  }
    780  CSSOrderAwareFrameIterator iter(
    781      parent, FrameChildListID::Principal,
    782      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
    783      CSSOrderAwareFrameIterator::OrderState::Unknown,
    784      CSSOrderAwareFrameIterator::OrderingProperty::Order);
    785 
    786  nsIFrame* prevSibling = nullptr;
    787  for (; !iter.AtEnd(); iter.Next()) {
    788    nsIFrame* current = iter.get();
    789    if (!aNext && current == aBox) {
    790      return prevSibling;
    791    }
    792    if (aNext && prevSibling == aBox) {
    793      return current;
    794    }
    795    prevSibling = current;
    796  }
    797  return nullptr;
    798 }
    799 
    800 void nsSplitterFrameInner::UpdateState() {
    801  // State Transitions:
    802  //   Open            -> Dragging
    803  //   Open            -> CollapsedBefore
    804  //   Open            -> CollapsedAfter
    805  //   CollapsedBefore -> Open
    806  //   CollapsedBefore -> Dragging
    807  //   CollapsedAfter  -> Open
    808  //   CollapsedAfter  -> Dragging
    809  //   Dragging        -> Open
    810  //   Dragging        -> CollapsedBefore (auto collapse)
    811  //   Dragging        -> CollapsedAfter (auto collapse)
    812 
    813  State newState = GetState();
    814 
    815  if (newState == mState) {
    816    // No change.
    817    return;
    818  }
    819 
    820  if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
    821      IsValidParentBox(mOuter->GetParent())) {
    822    // Find the splitter's immediate sibling.
    823    const bool prev =
    824        newState == State::CollapsedBefore || mState == State::CollapsedBefore;
    825    nsIFrame* splitterSibling = SlowOrderAwareSibling(mOuter, !prev);
    826    if (splitterSibling) {
    827      nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
    828      if (sibling && sibling->IsElement()) {
    829        if (mState == State::CollapsedBefore ||
    830            mState == State::CollapsedAfter) {
    831          // CollapsedBefore -> Open
    832          // CollapsedBefore -> Dragging
    833          // CollapsedAfter -> Open
    834          // CollapsedAfter -> Dragging
    835          nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
    836              sibling->AsElement(), nsGkAtoms::collapsed));
    837        } else if ((mState == State::Open || mState == State::Dragging) &&
    838                   (newState == State::CollapsedBefore ||
    839                    newState == State::CollapsedAfter)) {
    840          // Open -> CollapsedBefore / CollapsedAfter
    841          // Dragging -> CollapsedBefore / CollapsedAfter
    842          nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
    843              sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
    844        }
    845      }
    846    }
    847  }
    848  mState = newState;
    849 }
    850 
    851 void nsSplitterFrameInner::EnsureOrient() {
    852  mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
    853 }
    854 
    855 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
    856  EnsureOrient();
    857  const bool isHorizontal = !mOuter->IsHorizontal();
    858 
    859  AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal);
    860  AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal);
    861 }
    862 
    863 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
    864                                       nsIContent* aContent) {
    865  // XXX Can this use GetPrimaryFrame?
    866  for (nsIFrame* f : aParentBox->PrincipalChildList()) {
    867    if (f->GetContent() == aContent) {
    868      return f;
    869    }
    870  }
    871  return nullptr;
    872 }
    873 
    874 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
    875                                          nsTArray<nsSplitterInfo>& aChildInfos,
    876                                          bool aIsHorizontal) {
    877  /// printf("------- AdjustChildren------\n");
    878 
    879  for (auto& info : aChildInfos) {
    880    nscoord newPref = info.pref + (info.changed - info.current);
    881    if (nsIFrame* childBox =
    882            GetChildBoxForContent(mParentBox, info.childElem)) {
    883      SetPreferredSize(childBox, aIsHorizontal, newPref);
    884    }
    885  }
    886 }
    887 
    888 void nsSplitterFrameInner::SetPreferredSize(nsIFrame* aChildBox,
    889                                            bool aIsHorizontal, nscoord aSize) {
    890  nsMargin margin;
    891  aChildBox->StyleMargin()->GetMargin(margin);
    892  if (aIsHorizontal) {
    893    aSize -= (margin.left + margin.right);
    894  } else {
    895    aSize -= (margin.top + margin.bottom);
    896  }
    897 
    898  RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
    899  if (!element) {
    900    return;
    901  }
    902 
    903  // We set both the attribute and the CSS value, so that XUL persist="" keeps
    904  // working, see bug 1790712.
    905 
    906  int32_t pixels = aSize / AppUnitsPerCSSPixel();
    907  nsAutoString attrValue;
    908  attrValue.AppendInt(pixels);
    909  element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height,
    910                   attrValue, IgnoreErrors());
    911 
    912  nsCOMPtr<nsDOMCSSDeclaration> decl = element->Style();
    913 
    914  nsAutoCString cssValue;
    915  cssValue.AppendInt(pixels);
    916  cssValue.AppendLiteral("px");
    917  decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns,
    918                    IgnoreErrors());
    919 }
    920 
    921 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
    922                                          nsTArray<nsSplitterInfo>& aChildInfos,
    923                                          int32_t& aSpaceLeft) {
    924  aSpaceLeft = 0;
    925 
    926  for (auto& info : aChildInfos) {
    927    nscoord min = info.min;
    928    nscoord max = info.max;
    929    nscoord& c = info.changed;
    930 
    931    // figure our how much space to add or remove
    932    if (c + aDiff < min) {
    933      aDiff += (c - min);
    934      c = min;
    935    } else if (c + aDiff > max) {
    936      aDiff -= (max - c);
    937      c = max;
    938    } else {
    939      c += aDiff;
    940      aDiff = 0;
    941    }
    942 
    943    // there is not space left? We are done
    944    if (aDiff == 0) {
    945      break;
    946    }
    947  }
    948 
    949  aSpaceLeft = aDiff;
    950 }
    951 
    952 /**
    953 * Ok if we want to resize a child we will know the actual size in pixels we
    954 * want it to be. This is not the preferred size. But the only way we can change
    955 * a child is by manipulating its preferred size. So give the actual pixel size
    956 * this method will figure out the preferred size and set it.
    957 */
    958 
    959 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) {
    960  nscoord spaceLeft = 0;
    961 
    962  if (!mChildInfosBefore.IsEmpty()) {
    963    AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft);
    964    // If there is any space left over remove it from the diff we were
    965    // originally given.
    966    aDiff -= spaceLeft;
    967  }
    968 
    969  AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
    970 
    971  if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
    972    aDiff += spaceLeft;
    973    AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);
    974  }
    975 }