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