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 "nsBoxLayoutState.h"
27 #include "nsContainerFrame.h"
28 #include "nsContentCID.h"
29 #include "nsLayoutUtils.h"
30 #include "nsDisplayList.h"
31 #include "nsContentUtils.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/Event.h"
34 #include "mozilla/dom/MouseEvent.h"
35 #include "mozilla/MouseEvents.h"
36 #include "mozilla/PresShell.h"
37 #include "mozilla/UniquePtr.h"
38 
39 using namespace mozilla;
40 
41 using mozilla::dom::Element;
42 using mozilla::dom::Event;
43 
44 class nsSplitterInfo {
45  public:
46   nscoord min;
47   nscoord max;
48   nscoord current;
49   nscoord changed;
50   nsCOMPtr<nsIContent> childElem;
51   int32_t flex;
52   int32_t index;
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                                        NS_LITERAL_STRING("vertical"), 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 (GetStateBits() & 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::eLeft) {
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(
360           kNameSpaceID_None, nsGkAtoms::state, EmptyString(), 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     bool bounded;
403 
404     if (resizeAfter == nsSplitterFrameInner::Grow)
405       bounded = false;
406     else
407       bounded = true;
408 
409     int i;
410     for (i = 0; i < mChildInfosBeforeCount; i++)
411       mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
412 
413     for (i = 0; i < mChildInfosAfterCount; i++)
414       mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
415 
416     nscoord oldPos = pos;
417 
418     ResizeChildTo(pos, mChildInfosBefore.get(), mChildInfosAfter.get(),
419                   mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
420 
421     State currentState = GetState();
422     bool supportsBefore = SupportsCollapseDirection(Before);
423     bool supportsAfter = SupportsCollapseDirection(After);
424 
425     const bool isRTL =
426         mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
427     bool pastEnd = oldPos > 0 && oldPos > pos;
428     bool pastBegin = oldPos < 0 && oldPos < pos;
429     if (isRTL) {
430       // Swap the boundary checks in RTL mode
431       bool tmp = pastEnd;
432       pastEnd = pastBegin;
433       pastBegin = tmp;
434     }
435     const bool isCollapsedBefore = pastBegin && supportsBefore;
436     const bool isCollapsedAfter = pastEnd && supportsAfter;
437 
438     // if we are in a collapsed position
439     if (isCollapsedBefore || isCollapsedAfter) {
440       // and we are not collapsed then collapse
441       if (currentState == Dragging) {
442         if (pastEnd) {
443           // printf("Collapse right\n");
444           if (supportsAfter) {
445             RefPtr<Element> outer = mOuter->mContent->AsElement();
446             outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
447                            NS_LITERAL_STRING("after"), true);
448             outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
449                            NS_LITERAL_STRING("collapsed"), true);
450           }
451 
452         } else if (pastBegin) {
453           // printf("Collapse left\n");
454           if (supportsBefore) {
455             RefPtr<Element> outer = mOuter->mContent->AsElement();
456             outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
457                            NS_LITERAL_STRING("before"), true);
458             outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
459                            NS_LITERAL_STRING("collapsed"), true);
460           }
461         }
462       }
463     } else {
464       // if we are not in a collapsed position and we are not dragging make sure
465       // we are dragging.
466       if (currentState != Dragging) {
467         mOuter->mContent->AsElement()->SetAttr(
468             kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"),
469             true);
470       }
471       AdjustChildren(aPresContext);
472     }
473 
474     mDidDrag = true;
475   }
476 }
477 
AddListener()478 void nsSplitterFrameInner::AddListener() {
479   mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseup"), this,
480                                          false, false);
481   mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
482                                          false, false);
483   mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
484                                          false, false);
485   mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseout"), this,
486                                          false, false);
487 }
488 
RemoveListener()489 void nsSplitterFrameInner::RemoveListener() {
490   NS_ENSURE_TRUE_VOID(mOuter);
491   mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this,
492                                             false);
493   mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
494                                             this, false);
495   mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
496                                             this, false);
497   mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this,
498                                             false);
499 }
500 
HandleEvent(dom::Event * aEvent)501 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
502   nsAutoString eventType;
503   aEvent->GetType(eventType);
504   if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
505   if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
506   if (eventType.EqualsLiteral("mousemove") ||
507       eventType.EqualsLiteral("mouseout"))
508     return MouseMove(aEvent);
509 
510   MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
511   return NS_OK;
512 }
513 
MouseUp(Event * aMouseEvent)514 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
515   NS_ENSURE_TRUE(mOuter, NS_OK);
516   mPressed = false;
517 
518   PresShell::ReleaseCapturingContent();
519 
520   return NS_OK;
521 }
522 
MouseDown(Event * aMouseEvent)523 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
524   NS_ENSURE_TRUE(mOuter, NS_OK);
525   dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
526   if (!mouseEvent) {
527     return NS_OK;
528   }
529 
530   // only if left button
531   if (mouseEvent->Button() != 0) return NS_OK;
532 
533   if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
534                                      nsGkAtoms::_true, eCaseMatters))
535     return NS_OK;
536 
537   mParentBox = nsIFrame::GetParentXULBox(mOuter);
538   if (!mParentBox) return NS_OK;
539 
540   // get our index
541   nsPresContext* outerPresContext = mOuter->PresContext();
542   const nsFrameList& siblingList(mParentBox->PrincipalChildList());
543   int32_t childIndex = siblingList.IndexOf(mOuter);
544   // if it's 0 (or not found) then stop right here.
545   // It might be not found if we're not in the parent's primary frame list.
546   if (childIndex <= 0) return NS_OK;
547 
548   int32_t childCount = siblingList.GetLength();
549   // if it's the last index then we need to allow for resizeafter="grow"
550   if (childIndex == childCount - 1 && GetResizeAfter() != Grow) return NS_OK;
551 
552   RefPtr<gfxContext> rc =
553       outerPresContext->PresShell()->CreateReferenceRenderingContext();
554   nsBoxLayoutState state(outerPresContext, rc);
555   mPressed = true;
556 
557   mDidDrag = false;
558 
559   EnsureOrient();
560   bool isHorizontal = !mOuter->IsXULHorizontal();
561 
562   ResizeType resizeBefore = GetResizeBefore();
563   ResizeType resizeAfter = GetResizeAfter();
564 
565   mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount);
566   mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount);
567 
568   // create info 2 lists. One of the children before us and one after.
569   int32_t count = 0;
570   mChildInfosBeforeCount = 0;
571   mChildInfosAfterCount = 0;
572 
573   nsIFrame* childBox = nsIFrame::GetChildXULBox(mParentBox);
574 
575   while (childBox) {
576     nsIContent* content = childBox->GetContent();
577 
578     // skip over any splitters
579     if (content->NodeInfo()->NameAtom() != nsGkAtoms::splitter) {
580       nsSize prefSize = childBox->GetXULPrefSize(state);
581       nsSize minSize = childBox->GetXULMinSize(state);
582       nsSize maxSize = nsIFrame::XULBoundsCheckMinMax(
583           minSize, childBox->GetXULMaxSize(state));
584       prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize);
585 
586       nsSplitterFrame::AddXULMargin(childBox, minSize);
587       nsSplitterFrame::AddXULMargin(childBox, prefSize);
588       nsSplitterFrame::AddXULMargin(childBox, maxSize);
589 
590       nscoord flex = childBox->GetXULFlex();
591 
592       nsMargin margin(0, 0, 0, 0);
593       childBox->GetXULMargin(margin);
594       nsRect r(childBox->GetRect());
595       r.Inflate(margin);
596 
597       // We need to check for hidden attribute too, since treecols with
598       // the hidden="true" attribute are not really hidden, just collapsed
599       if (!content->IsElement() || (!content->AsElement()->AttrValueIs(
600                                         kNameSpaceID_None, nsGkAtoms::fixed,
601                                         nsGkAtoms::_true, eCaseMatters) &&
602                                     !content->AsElement()->AttrValueIs(
603                                         kNameSpaceID_None, nsGkAtoms::hidden,
604                                         nsGkAtoms::_true, eCaseMatters))) {
605         if (count < childIndex && (resizeBefore != Flex || flex > 0)) {
606           mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
607           mChildInfosBefore[mChildInfosBeforeCount].min =
608               isHorizontal ? minSize.width : minSize.height;
609           mChildInfosBefore[mChildInfosBeforeCount].max =
610               isHorizontal ? maxSize.width : maxSize.height;
611           mChildInfosBefore[mChildInfosBeforeCount].current =
612               isHorizontal ? r.width : r.height;
613           mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
614           mChildInfosBefore[mChildInfosBeforeCount].index = count;
615           mChildInfosBefore[mChildInfosBeforeCount].changed =
616               mChildInfosBefore[mChildInfosBeforeCount].current;
617           mChildInfosBeforeCount++;
618         } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) {
619           mChildInfosAfter[mChildInfosAfterCount].childElem = content;
620           mChildInfosAfter[mChildInfosAfterCount].min =
621               isHorizontal ? minSize.width : minSize.height;
622           mChildInfosAfter[mChildInfosAfterCount].max =
623               isHorizontal ? maxSize.width : maxSize.height;
624           mChildInfosAfter[mChildInfosAfterCount].current =
625               isHorizontal ? r.width : r.height;
626           mChildInfosAfter[mChildInfosAfterCount].flex = flex;
627           mChildInfosAfter[mChildInfosAfterCount].index = count;
628           mChildInfosAfter[mChildInfosAfterCount].changed =
629               mChildInfosAfter[mChildInfosAfterCount].current;
630           mChildInfosAfterCount++;
631         }
632       }
633     }
634 
635     childBox = nsIFrame::GetNextXULBox(childBox);
636     count++;
637   }
638 
639   if (!mParentBox->IsXULNormalDirection()) {
640     // The before array is really the after array, and the order needs to be
641     // reversed. First reverse both arrays.
642     Reverse(mChildInfosBefore, mChildInfosBeforeCount);
643     Reverse(mChildInfosAfter, mChildInfosAfterCount);
644 
645     // Now swap the two arrays.
646     std::swap(mChildInfosBeforeCount, mChildInfosAfterCount);
647     std::swap(mChildInfosBefore, mChildInfosAfter);
648   }
649 
650   // if resizebefore is not Farthest, reverse the list because the first child
651   // in the list is the farthest, and we want the first child to be the closest.
652   if (resizeBefore != Farthest)
653     Reverse(mChildInfosBefore, mChildInfosBeforeCount);
654 
655   // if the resizeafter is the Farthest we must reverse the list because the
656   // first child in the list is the closest we want the first child to be the
657   // Farthest.
658   if (resizeAfter == Farthest) Reverse(mChildInfosAfter, mChildInfosAfterCount);
659 
660   // grow only applys to the children after. If grow is set then no space should
661   // be taken out of any children after us. To do this we just set the size of
662   // that list to be 0.
663   if (resizeAfter == Grow) mChildInfosAfterCount = 0;
664 
665   int32_t c;
666   nsPoint pt =
667       nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
668   if (isHorizontal) {
669     c = pt.x;
670     mSplitterPos = mOuter->mRect.x;
671   } else {
672     c = pt.y;
673     mSplitterPos = mOuter->mRect.y;
674   }
675 
676   mDragStart = c;
677 
678   // printf("Pressed mDragStart=%d\n",mDragStart);
679 
680   PresShell::SetCapturingContent(mOuter->GetContent(),
681                                  CaptureFlags::IgnoreAllowedState);
682 
683   return NS_OK;
684 }
685 
MouseMove(Event * aMouseEvent)686 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
687   NS_ENSURE_TRUE(mOuter, NS_OK);
688   if (!mPressed) return NS_OK;
689 
690   if (mDragging) return NS_OK;
691 
692   nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
693   mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
694                                          NS_LITERAL_STRING("dragging"), true);
695 
696   RemoveListener();
697   mDragging = true;
698 
699   return NS_OK;
700 }
701 
Reverse(UniquePtr<nsSplitterInfo[]> & aChildInfos,int32_t aCount)702 void nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos,
703                                    int32_t aCount) {
704   UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]);
705 
706   for (int i = 0; i < aCount; i++) infos[i] = aChildInfos[aCount - 1 - i];
707 
708   aChildInfos = std::move(infos);
709 }
710 
SupportsCollapseDirection(nsSplitterFrameInner::CollapseDirection aDirection)711 bool nsSplitterFrameInner::SupportsCollapseDirection(
712     nsSplitterFrameInner::CollapseDirection aDirection) {
713   static Element::AttrValuesArray strings[] = {
714       nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
715 
716   switch (SplitterElement()->FindAttrValueIn(
717       kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
718     case 0:
719       return (aDirection == Before);
720     case 1:
721       return (aDirection == After);
722     case 2:
723       return true;
724   }
725 
726   return false;
727 }
728 
UpdateState()729 void nsSplitterFrameInner::UpdateState() {
730   // State Transitions:
731   //   Open            -> Dragging
732   //   Open            -> CollapsedBefore
733   //   Open            -> CollapsedAfter
734   //   CollapsedBefore -> Open
735   //   CollapsedBefore -> Dragging
736   //   CollapsedAfter  -> Open
737   //   CollapsedAfter  -> Dragging
738   //   Dragging        -> Open
739   //   Dragging        -> CollapsedBefore (auto collapse)
740   //   Dragging        -> CollapsedAfter (auto collapse)
741 
742   State newState = GetState();
743 
744   if (newState == mState) {
745     // No change.
746     return;
747   }
748 
749   if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
750       mOuter->GetParent()->IsXULBoxFrame()) {
751     // Find the splitter's immediate sibling.
752     nsIFrame* splitterSibling;
753     if (newState == CollapsedBefore || mState == CollapsedBefore) {
754       splitterSibling = mOuter->GetPrevSibling();
755     } else {
756       splitterSibling = mOuter->GetNextSibling();
757     }
758 
759     if (splitterSibling) {
760       nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
761       if (sibling && sibling->IsElement()) {
762         if (mState == CollapsedBefore || mState == CollapsedAfter) {
763           // CollapsedBefore -> Open
764           // CollapsedBefore -> Dragging
765           // CollapsedAfter -> Open
766           // CollapsedAfter -> Dragging
767           nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
768               sibling->AsElement(), nsGkAtoms::collapsed));
769         } else if ((mState == Open || mState == Dragging) &&
770                    (newState == CollapsedBefore ||
771                     newState == CollapsedAfter)) {
772           // Open -> CollapsedBefore / CollapsedAfter
773           // Dragging -> CollapsedBefore / CollapsedAfter
774           nsContentUtils::AddScriptRunner(
775               new nsSetAttrRunnable(sibling->AsElement(), nsGkAtoms::collapsed,
776                                     NS_LITERAL_STRING("true")));
777         }
778       }
779     }
780   }
781   mState = newState;
782 }
783 
EnsureOrient()784 void nsSplitterFrameInner::EnsureOrient() {
785   bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL);
786   if (isHorizontal)
787     mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL);
788   else
789     mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL);
790 }
791 
AdjustChildren(nsPresContext * aPresContext)792 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
793   EnsureOrient();
794   bool isHorizontal = !mOuter->IsXULHorizontal();
795 
796   AdjustChildren(aPresContext, mChildInfosBefore.get(), mChildInfosBeforeCount,
797                  isHorizontal);
798   AdjustChildren(aPresContext, mChildInfosAfter.get(), mChildInfosAfterCount,
799                  isHorizontal);
800 }
801 
GetChildBoxForContent(nsIFrame * aParentBox,nsIContent * aContent)802 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
803                                        nsIContent* aContent) {
804   nsIFrame* childBox = nsIFrame::GetChildXULBox(aParentBox);
805 
806   while (nullptr != childBox) {
807     if (childBox->GetContent() == aContent) {
808       return childBox;
809     }
810     childBox = nsIFrame::GetNextXULBox(childBox);
811   }
812   return nullptr;
813 }
814 
AdjustChildren(nsPresContext * aPresContext,nsSplitterInfo * aChildInfos,int32_t aCount,bool aIsHorizontal)815 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
816                                           nsSplitterInfo* aChildInfos,
817                                           int32_t aCount, bool aIsHorizontal) {
818   /// printf("------- AdjustChildren------\n");
819 
820   nsBoxLayoutState state(aPresContext);
821 
822   nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
823 
824   // first set all the widths.
825   nsIFrame* child = nsIFrame::GetChildXULBox(mOuter);
826   while (child) {
827     SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
828     child = nsIFrame::GetNextXULBox(child);
829   }
830 
831   // now set our changed widths.
832   for (int i = 0; i < aCount; i++) {
833     nscoord pref = aChildInfos[i].changed;
834     nsIFrame* childBox =
835         GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
836 
837     if (childBox) {
838       SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
839     }
840   }
841 }
842 
SetPreferredSize(nsBoxLayoutState & aState,nsIFrame * aChildBox,nscoord aOnePixel,bool aIsHorizontal,nscoord * aSize)843 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState,
844                                             nsIFrame* aChildBox,
845                                             nscoord aOnePixel,
846                                             bool aIsHorizontal,
847                                             nscoord* aSize) {
848   nsRect rect(aChildBox->GetRect());
849   nscoord pref = 0;
850 
851   if (!aSize) {
852     if (aIsHorizontal)
853       pref = rect.width;
854     else
855       pref = rect.height;
856   } else {
857     pref = *aSize;
858   }
859 
860   nsMargin margin(0, 0, 0, 0);
861   aChildBox->GetXULMargin(margin);
862 
863   RefPtr<nsAtom> attribute;
864 
865   if (aIsHorizontal) {
866     pref -= (margin.left + margin.right);
867     attribute = nsGkAtoms::width;
868   } else {
869     pref -= (margin.top + margin.bottom);
870     attribute = nsGkAtoms::height;
871   }
872 
873   nsIContent* content = aChildBox->GetContent();
874   if (!content->IsElement()) {
875     return;
876   }
877 
878   // set its preferred size.
879   nsAutoString prefValue;
880   prefValue.AppendInt(pref / aOnePixel);
881   if (content->AsElement()->AttrValueIs(kNameSpaceID_None, attribute, prefValue,
882                                         eCaseMatters)) {
883     return;
884   }
885 
886   AutoWeakFrame weakBox(aChildBox);
887   content->AsElement()->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
888   NS_ENSURE_TRUE_VOID(weakBox.IsAlive());
889   aState.PresShell()->FrameNeedsReflow(aChildBox, IntrinsicDirty::StyleChange,
890                                        NS_FRAME_IS_DIRTY);
891 }
892 
AddRemoveSpace(nscoord aDiff,nsSplitterInfo * aChildInfos,int32_t aCount,int32_t & aSpaceLeft)893 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
894                                           nsSplitterInfo* aChildInfos,
895                                           int32_t aCount, int32_t& aSpaceLeft) {
896   aSpaceLeft = 0;
897 
898   for (int i = 0; i < aCount; i++) {
899     nscoord min = aChildInfos[i].min;
900     nscoord max = aChildInfos[i].max;
901     nscoord& c = aChildInfos[i].changed;
902 
903     // figure our how much space to add or remove
904     if (c + aDiff < min) {
905       aDiff += (c - min);
906       c = min;
907     } else if (c + aDiff > max) {
908       aDiff -= (max - c);
909       c = max;
910     } else {
911       c += aDiff;
912       aDiff = 0;
913     }
914 
915     // there is not space left? We are done
916     if (aDiff == 0) break;
917   }
918 
919   aSpaceLeft = aDiff;
920 }
921 
922 /**
923  * Ok if we want to resize a child we will know the actual size in pixels we
924  * want it to be. This is not the preferred size. But they only way we can
925  * change a child is my manipulating its preferred size. So give the actual
926  * pixel size this return method will return figure out the preferred size and
927  * set it.
928  */
929 
ResizeChildTo(nscoord & aDiff,nsSplitterInfo * aChildrenBeforeInfos,nsSplitterInfo * aChildrenAfterInfos,int32_t aChildrenBeforeCount,int32_t aChildrenAfterCount,bool aBounded)930 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff,
931                                          nsSplitterInfo* aChildrenBeforeInfos,
932                                          nsSplitterInfo* aChildrenAfterInfos,
933                                          int32_t aChildrenBeforeCount,
934                                          int32_t aChildrenAfterCount,
935                                          bool aBounded) {
936   nscoord spaceLeft;
937   AddRemoveSpace(aDiff, aChildrenBeforeInfos, aChildrenBeforeCount, spaceLeft);
938 
939   // if there is any space left over remove it from the dif we were originally
940   // given
941   aDiff -= spaceLeft;
942   AddRemoveSpace(-aDiff, aChildrenAfterInfos, aChildrenAfterCount, spaceLeft);
943 
944   if (spaceLeft != 0) {
945     if (aBounded) {
946       aDiff += spaceLeft;
947       AddRemoveSpace(spaceLeft, aChildrenBeforeInfos, aChildrenBeforeCount,
948                      spaceLeft);
949     }
950   }
951 }
952