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