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 #include "nsNameSpaceManager.h"
8 #include "nsGkAtoms.h"
9 #include "nsIBoxObject.h"
10 #include "nsTreeUtils.h"
11 #include "nsTreeContentView.h"
12 #include "ChildIterator.h"
13 #include "nsError.h"
14 #include "nsIXULSortService.h"
15 #include "nsTreeBodyFrame.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/TreeContentViewBinding.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsIDocument.h"
21
22 using namespace mozilla;
23
24 // A content model view implementation for the tree.
25
26 #define ROW_FLAG_CONTAINER 0x01
27 #define ROW_FLAG_OPEN 0x02
28 #define ROW_FLAG_EMPTY 0x04
29 #define ROW_FLAG_SEPARATOR 0x08
30
31 class Row {
32 public:
Row(Element * aContent,int32_t aParentIndex)33 Row(Element* aContent, int32_t aParentIndex)
34 : mContent(aContent),
35 mParentIndex(aParentIndex),
36 mSubtreeSize(0),
37 mFlags(0) {}
38
~Row()39 ~Row() {}
40
SetContainer(bool aContainer)41 void SetContainer(bool aContainer) {
42 aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER;
43 }
IsContainer()44 bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; }
45
SetOpen(bool aOpen)46 void SetOpen(bool aOpen) {
47 aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN;
48 }
IsOpen()49 bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); }
50
SetEmpty(bool aEmpty)51 void SetEmpty(bool aEmpty) {
52 aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY;
53 }
IsEmpty()54 bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); }
55
SetSeparator(bool aSeparator)56 void SetSeparator(bool aSeparator) {
57 aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR;
58 }
IsSeparator()59 bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); }
60
61 // Weak reference to a content item.
62 Element* mContent;
63
64 // The parent index of the item, set to -1 for the top level items.
65 int32_t mParentIndex;
66
67 // Subtree size for this item.
68 int32_t mSubtreeSize;
69
70 private:
71 // State flags
72 int8_t mFlags;
73 };
74
75 // We don't reference count the reference to the document
76 // If the document goes away first, we'll be informed and we
77 // can drop our reference.
78 // If we go away first, we'll get rid of ourselves from the
79 // document's observer list.
80
nsTreeContentView(void)81 nsTreeContentView::nsTreeContentView(void)
82 : mBoxObject(nullptr),
83 mSelection(nullptr),
84 mRoot(nullptr),
85 mDocument(nullptr) {}
86
~nsTreeContentView(void)87 nsTreeContentView::~nsTreeContentView(void) {
88 // Remove ourselves from mDocument's observers.
89 if (mDocument) mDocument->RemoveObserver(this);
90 }
91
NS_NewTreeContentView(nsITreeView ** aResult)92 nsresult NS_NewTreeContentView(nsITreeView** aResult) {
93 *aResult = new nsTreeContentView;
94 if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
95 NS_ADDREF(*aResult);
96 return NS_OK;
97 }
98
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView,mBoxObject,mSelection,mRoot,mBody)99 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView, mBoxObject, mSelection,
100 mRoot, mBody)
101
102 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView)
103 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView)
104
105 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView)
106 NS_INTERFACE_MAP_ENTRY(nsITreeView)
107 NS_INTERFACE_MAP_ENTRY(nsITreeContentView)
108 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
109 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
110 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView)
111 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
112 NS_INTERFACE_MAP_END
113
114 JSObject* nsTreeContentView::WrapObject(JSContext* aCx,
115 JS::Handle<JSObject*> aGivenProto) {
116 return TreeContentViewBinding::Wrap(aCx, this, aGivenProto);
117 }
118
GetParentObject()119 nsISupports* nsTreeContentView::GetParentObject() { return mBoxObject; }
120
121 NS_IMETHODIMP
GetRowCount(int32_t * aRowCount)122 nsTreeContentView::GetRowCount(int32_t* aRowCount) {
123 *aRowCount = mRows.Length();
124
125 return NS_OK;
126 }
127
128 NS_IMETHODIMP
GetSelection(nsITreeSelection ** aSelection)129 nsTreeContentView::GetSelection(nsITreeSelection** aSelection) {
130 NS_IF_ADDREF(*aSelection = GetSelection());
131
132 return NS_OK;
133 }
134
CanTrustTreeSelection(nsISupports * aValue)135 bool nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) {
136 // Untrusted content is only allowed to specify known-good views
137 if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) return true;
138 nsCOMPtr<nsINativeTreeSelection> nativeTreeSel = do_QueryInterface(aValue);
139 return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative());
140 }
141
142 NS_IMETHODIMP
SetSelection(nsITreeSelection * aSelection)143 nsTreeContentView::SetSelection(nsITreeSelection* aSelection) {
144 ErrorResult rv;
145 SetSelection(aSelection, rv);
146 return rv.StealNSResult();
147 }
148
SetSelection(nsITreeSelection * aSelection,ErrorResult & aError)149 void nsTreeContentView::SetSelection(nsITreeSelection* aSelection,
150 ErrorResult& aError) {
151 if (aSelection && !CanTrustTreeSelection(aSelection)) {
152 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
153 return;
154 }
155
156 mSelection = aSelection;
157 }
158
GetRowProperties(int32_t aRow,nsAString & aProperties,ErrorResult & aError)159 void nsTreeContentView::GetRowProperties(int32_t aRow, nsAString& aProperties,
160 ErrorResult& aError) {
161 aProperties.Truncate();
162 if (!IsValidRowIndex(aRow)) {
163 aError.Throw(NS_ERROR_INVALID_ARG);
164 return;
165 }
166
167 Row* row = mRows[aRow].get();
168 nsIContent* realRow;
169 if (row->IsSeparator())
170 realRow = row->mContent;
171 else
172 realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
173
174 if (realRow && realRow->IsElement()) {
175 realRow->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::properties,
176 aProperties);
177 }
178 }
179
180 NS_IMETHODIMP
GetRowProperties(int32_t aIndex,nsAString & aProps)181 nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) {
182 ErrorResult rv;
183 GetRowProperties(aIndex, aProps, rv);
184 return rv.StealNSResult();
185 }
186
GetCellProperties(int32_t aRow,nsTreeColumn & aColumn,nsAString & aProperties,ErrorResult & aError)187 void nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn& aColumn,
188 nsAString& aProperties,
189 ErrorResult& aError) {
190 if (!IsValidRowIndex(aRow)) {
191 aError.Throw(NS_ERROR_INVALID_ARG);
192 return;
193 }
194
195 Row* row = mRows[aRow].get();
196 nsIContent* realRow =
197 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
198 if (realRow) {
199 Element* cell = GetCell(realRow, aColumn);
200 if (cell) {
201 cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProperties);
202 }
203 }
204 }
205
206 NS_IMETHODIMP
GetCellProperties(int32_t aRow,nsITreeColumn * aCol,nsAString & aProps)207 nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol,
208 nsAString& aProps) {
209 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
210 NS_ENSURE_ARG(col);
211
212 ErrorResult rv;
213 GetCellProperties(aRow, *col, aProps, rv);
214 return rv.StealNSResult();
215 }
216
GetColumnProperties(nsTreeColumn & aColumn,nsAString & aProperties)217 void nsTreeContentView::GetColumnProperties(nsTreeColumn& aColumn,
218 nsAString& aProperties) {
219 RefPtr<Element> element = aColumn.GetElement(IgnoreErrors());
220
221 if (element) {
222 element->GetAttribute(NS_LITERAL_STRING("properties"), aProperties);
223 }
224 }
225
226 NS_IMETHODIMP
GetColumnProperties(nsITreeColumn * aCol,nsAString & aProps)227 nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) {
228 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
229 NS_ENSURE_ARG(col);
230
231 GetColumnProperties(*col, aProps);
232 return NS_OK;
233 }
234
IsContainer(int32_t aRow,ErrorResult & aError)235 bool nsTreeContentView::IsContainer(int32_t aRow, ErrorResult& aError) {
236 if (!IsValidRowIndex(aRow)) {
237 aError.Throw(NS_ERROR_INVALID_ARG);
238 return false;
239 }
240
241 return mRows[aRow]->IsContainer();
242 }
243
244 NS_IMETHODIMP
IsContainer(int32_t aIndex,bool * _retval)245 nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) {
246 ErrorResult rv;
247 *_retval = IsContainer(aIndex, rv);
248 return rv.StealNSResult();
249 }
250
IsContainerOpen(int32_t aRow,ErrorResult & aError)251 bool nsTreeContentView::IsContainerOpen(int32_t aRow, ErrorResult& aError) {
252 if (!IsValidRowIndex(aRow)) {
253 aError.Throw(NS_ERROR_INVALID_ARG);
254 return false;
255 }
256
257 return mRows[aRow]->IsOpen();
258 }
259
260 NS_IMETHODIMP
IsContainerOpen(int32_t aIndex,bool * _retval)261 nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) {
262 ErrorResult rv;
263 *_retval = IsContainerOpen(aIndex, rv);
264 return rv.StealNSResult();
265 }
266
IsContainerEmpty(int32_t aRow,ErrorResult & aError)267 bool nsTreeContentView::IsContainerEmpty(int32_t aRow, ErrorResult& aError) {
268 if (!IsValidRowIndex(aRow)) {
269 aError.Throw(NS_ERROR_INVALID_ARG);
270 return false;
271 }
272
273 return mRows[aRow]->IsEmpty();
274 }
275
276 NS_IMETHODIMP
IsContainerEmpty(int32_t aIndex,bool * _retval)277 nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) {
278 ErrorResult rv;
279 *_retval = IsContainerEmpty(aIndex, rv);
280 return rv.StealNSResult();
281 }
282
IsSeparator(int32_t aRow,ErrorResult & aError)283 bool nsTreeContentView::IsSeparator(int32_t aRow, ErrorResult& aError) {
284 if (!IsValidRowIndex(aRow)) {
285 aError.Throw(NS_ERROR_INVALID_ARG);
286 return false;
287 }
288
289 return mRows[aRow]->IsSeparator();
290 }
291
292 NS_IMETHODIMP
IsSeparator(int32_t aIndex,bool * _retval)293 nsTreeContentView::IsSeparator(int32_t aIndex, bool* _retval) {
294 ErrorResult rv;
295 *_retval = IsSeparator(aIndex, rv);
296 return rv.StealNSResult();
297 }
298
299 NS_IMETHODIMP
IsSorted(bool * _retval)300 nsTreeContentView::IsSorted(bool* _retval) {
301 *_retval = IsSorted();
302
303 return NS_OK;
304 }
305
CanDrop(int32_t aRow,int32_t aOrientation,DataTransfer * aDataTransfer,ErrorResult & aError)306 bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
307 DataTransfer* aDataTransfer,
308 ErrorResult& aError) {
309 if (!IsValidRowIndex(aRow)) {
310 aError.Throw(NS_ERROR_INVALID_ARG);
311 }
312 return false;
313 }
314
315 NS_IMETHODIMP
CanDrop(int32_t aIndex,int32_t aOrientation,nsIDOMDataTransfer * aDataTransfer,bool * _retval)316 nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation,
317 nsIDOMDataTransfer* aDataTransfer, bool* _retval) {
318 ErrorResult rv;
319 *_retval =
320 CanDrop(aIndex, aOrientation, DataTransfer::Cast(aDataTransfer), rv);
321 return rv.StealNSResult();
322 }
323
Drop(int32_t aRow,int32_t aOrientation,DataTransfer * aDataTransfer,ErrorResult & aError)324 void nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
325 DataTransfer* aDataTransfer, ErrorResult& aError) {
326 if (!IsValidRowIndex(aRow)) {
327 aError.Throw(NS_ERROR_INVALID_ARG);
328 }
329 }
330
331 NS_IMETHODIMP
Drop(int32_t aRow,int32_t aOrientation,nsIDOMDataTransfer * aDataTransfer)332 nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
333 nsIDOMDataTransfer* aDataTransfer) {
334 ErrorResult rv;
335 Drop(aRow, aOrientation, DataTransfer::Cast(aDataTransfer), rv);
336 return rv.StealNSResult();
337 }
338
GetParentIndex(int32_t aRow,ErrorResult & aError)339 int32_t nsTreeContentView::GetParentIndex(int32_t aRow, ErrorResult& aError) {
340 if (!IsValidRowIndex(aRow)) {
341 aError.Throw(NS_ERROR_INVALID_ARG);
342 return 0;
343 }
344
345 return mRows[aRow]->mParentIndex;
346 }
347
348 NS_IMETHODIMP
GetParentIndex(int32_t aRowIndex,int32_t * _retval)349 nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) {
350 ErrorResult rv;
351 *_retval = GetParentIndex(aRowIndex, rv);
352 return rv.StealNSResult();
353 }
354
HasNextSibling(int32_t aRow,int32_t aAfterIndex,ErrorResult & aError)355 bool nsTreeContentView::HasNextSibling(int32_t aRow, int32_t aAfterIndex,
356 ErrorResult& aError) {
357 if (!IsValidRowIndex(aRow)) {
358 aError.Throw(NS_ERROR_INVALID_ARG);
359 return false;
360 }
361
362 // We have a next sibling if the row is not the last in the subtree.
363 int32_t parentIndex = mRows[aRow]->mParentIndex;
364 if (parentIndex < 0) {
365 return uint32_t(aRow) < mRows.Length() - 1;
366 }
367
368 // Compute the last index in this subtree.
369 int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize;
370 Row* row = mRows[lastIndex].get();
371 while (row->mParentIndex != parentIndex) {
372 lastIndex = row->mParentIndex;
373 row = mRows[lastIndex].get();
374 }
375
376 return aRow < lastIndex;
377 }
378
379 NS_IMETHODIMP
HasNextSibling(int32_t aRowIndex,int32_t aAfterIndex,bool * _retval)380 nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
381 bool* _retval) {
382 ErrorResult rv;
383 *_retval = HasNextSibling(aRowIndex, aAfterIndex, rv);
384 return rv.StealNSResult();
385 }
386
GetLevel(int32_t aRow,ErrorResult & aError)387 int32_t nsTreeContentView::GetLevel(int32_t aRow, ErrorResult& aError) {
388 if (!IsValidRowIndex(aRow)) {
389 aError.Throw(NS_ERROR_INVALID_ARG);
390 return 0;
391 }
392
393 int32_t level = 0;
394 Row* row = mRows[aRow].get();
395 while (row->mParentIndex >= 0) {
396 level++;
397 row = mRows[row->mParentIndex].get();
398 }
399 return level;
400 }
401
402 NS_IMETHODIMP
GetLevel(int32_t aIndex,int32_t * _retval)403 nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) {
404 ErrorResult rv;
405 *_retval = GetLevel(aIndex, rv);
406 return rv.StealNSResult();
407 }
408
GetImageSrc(int32_t aRow,nsTreeColumn & aColumn,nsAString & aSrc,ErrorResult & aError)409 void nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn& aColumn,
410 nsAString& aSrc, ErrorResult& aError) {
411 if (!IsValidRowIndex(aRow)) {
412 aError.Throw(NS_ERROR_INVALID_ARG);
413 return;
414 }
415
416 Row* row = mRows[aRow].get();
417
418 nsIContent* realRow =
419 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
420 if (realRow) {
421 Element* cell = GetCell(realRow, aColumn);
422 if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, aSrc);
423 }
424 }
425
426 NS_IMETHODIMP
GetImageSrc(int32_t aRow,nsITreeColumn * aCol,nsAString & _retval)427 nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol,
428 nsAString& _retval) {
429 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
430 NS_ENSURE_ARG(col);
431
432 ErrorResult rv;
433 GetImageSrc(aRow, *col, _retval, rv);
434 return rv.StealNSResult();
435 }
436
GetCellValue(int32_t aRow,nsTreeColumn & aColumn,nsAString & aValue,ErrorResult & aError)437 void nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn& aColumn,
438 nsAString& aValue, ErrorResult& aError) {
439 if (!IsValidRowIndex(aRow)) {
440 aError.Throw(NS_ERROR_INVALID_ARG);
441 return;
442 }
443
444 Row* row = mRows[aRow].get();
445
446 nsIContent* realRow =
447 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
448 if (realRow) {
449 Element* cell = GetCell(realRow, aColumn);
450 if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
451 }
452 }
453
454 NS_IMETHODIMP
GetCellValue(int32_t aRow,nsITreeColumn * aCol,nsAString & _retval)455 nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol,
456 nsAString& _retval) {
457 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
458 NS_ENSURE_ARG(col);
459
460 ErrorResult rv;
461 GetCellValue(aRow, *col, _retval, rv);
462 return rv.StealNSResult();
463 }
464
GetCellText(int32_t aRow,nsTreeColumn & aColumn,nsAString & aText,ErrorResult & aError)465 void nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn& aColumn,
466 nsAString& aText, ErrorResult& aError) {
467 if (!IsValidRowIndex(aRow)) {
468 aError.Throw(NS_ERROR_INVALID_ARG);
469 return;
470 }
471
472 Row* row = mRows[aRow].get();
473
474 // Check for a "label" attribute - this is valid on an <treeitem>
475 // with a single implied column.
476 if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aText) &&
477 !aText.IsEmpty()) {
478 return;
479 }
480
481 if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) {
482 nsIContent* realRow =
483 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
484 if (realRow) {
485 Element* cell = GetCell(realRow, aColumn);
486 if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aText);
487 }
488 }
489 }
490
491 NS_IMETHODIMP
GetCellText(int32_t aRow,nsITreeColumn * aCol,nsAString & _retval)492 nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol,
493 nsAString& _retval) {
494 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
495 NS_ENSURE_ARG(col);
496
497 ErrorResult rv;
498 GetCellText(aRow, *col, _retval, rv);
499 return rv.StealNSResult();
500 }
501
SetTree(TreeBoxObject * aTree,ErrorResult & aError)502 void nsTreeContentView::SetTree(TreeBoxObject* aTree, ErrorResult& aError) {
503 aError = SetTree(aTree);
504 }
505
506 NS_IMETHODIMP
SetTree(nsITreeBoxObject * aTree)507 nsTreeContentView::SetTree(nsITreeBoxObject* aTree) {
508 ClearRows();
509
510 mBoxObject = aTree;
511
512 MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows");
513
514 if (aTree) {
515 // Get our root element
516 nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject);
517 if (!boxObject) {
518 mBoxObject = nullptr;
519 return NS_ERROR_INVALID_ARG;
520 }
521 nsCOMPtr<nsIDOMElement> element;
522 boxObject->GetElement(getter_AddRefs(element));
523
524 mRoot = do_QueryInterface(element);
525 NS_ENSURE_STATE(mRoot);
526
527 // Add ourselves to document's observers.
528 nsIDocument* document = mRoot->GetComposedDoc();
529 if (document) {
530 document->AddObserver(this);
531 mDocument = document;
532 }
533
534 nsCOMPtr<nsIDOMElement> bodyElement;
535 mBoxObject->GetTreeBody(getter_AddRefs(bodyElement));
536 if (bodyElement) {
537 mBody = do_QueryInterface(bodyElement);
538 int32_t index = 0;
539 Serialize(mBody, -1, &index, mRows);
540 }
541 }
542
543 return NS_OK;
544 }
545
ToggleOpenState(int32_t aRow,ErrorResult & aError)546 void nsTreeContentView::ToggleOpenState(int32_t aRow, ErrorResult& aError) {
547 if (!IsValidRowIndex(aRow)) {
548 aError.Throw(NS_ERROR_INVALID_ARG);
549 return;
550 }
551
552 // We don't serialize content right here, since content might be generated
553 // lazily.
554 Row* row = mRows[aRow].get();
555
556 if (row->IsOpen())
557 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
558 NS_LITERAL_STRING("false"), true);
559 else
560 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
561 NS_LITERAL_STRING("true"), true);
562 }
563
564 NS_IMETHODIMP
ToggleOpenState(int32_t aIndex)565 nsTreeContentView::ToggleOpenState(int32_t aIndex) {
566 ErrorResult rv;
567 ToggleOpenState(aIndex, rv);
568 return rv.StealNSResult();
569 }
570
CycleHeader(nsTreeColumn & aColumn,ErrorResult & aError)571 void nsTreeContentView::CycleHeader(nsTreeColumn& aColumn,
572 ErrorResult& aError) {
573 if (!mRoot) return;
574
575 nsCOMPtr<nsIDOMElement> element;
576 aColumn.GetElement(getter_AddRefs(element));
577 if (element) {
578 nsCOMPtr<Element> column = do_QueryInterface(element);
579 nsAutoString sort;
580 column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
581 if (!sort.IsEmpty()) {
582 nsCOMPtr<nsIXULSortService> xs =
583 do_GetService("@mozilla.org/xul/xul-sort-service;1");
584 if (xs) {
585 nsAutoString sortdirection;
586 static Element::AttrValuesArray strings[] = {
587 &nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr};
588 switch (column->FindAttrValueIn(kNameSpaceID_None,
589 nsGkAtoms::sortDirection, strings,
590 eCaseMatters)) {
591 case 0:
592 sortdirection.AssignLiteral("descending");
593 break;
594 case 1:
595 sortdirection.AssignLiteral("natural");
596 break;
597 default:
598 sortdirection.AssignLiteral("ascending");
599 break;
600 }
601
602 nsAutoString hints;
603 column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
604 sortdirection.Append(' ');
605 sortdirection += hints;
606
607 nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
608 xs->Sort(rootnode, sort, sortdirection);
609 }
610 }
611 }
612 }
613
614 NS_IMETHODIMP
CycleHeader(nsITreeColumn * aCol)615 nsTreeContentView::CycleHeader(nsITreeColumn* aCol) {
616 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
617 NS_ENSURE_ARG(col);
618
619 ErrorResult rv;
620 CycleHeader(*col, rv);
621 return rv.StealNSResult();
622 }
623
624 NS_IMETHODIMP
SelectionChanged()625 nsTreeContentView::SelectionChanged() { return NS_OK; }
626
627 NS_IMETHODIMP
CycleCell(int32_t aRow,nsITreeColumn * aCol)628 nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol) {
629 return NS_OK;
630 }
631
IsEditable(int32_t aRow,nsTreeColumn & aColumn,ErrorResult & aError)632 bool nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn& aColumn,
633 ErrorResult& aError) {
634 if (!IsValidRowIndex(aRow)) {
635 aError.Throw(NS_ERROR_INVALID_ARG);
636 return false;
637 }
638
639 Row* row = mRows[aRow].get();
640
641 nsIContent* realRow =
642 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
643 if (realRow) {
644 Element* cell = GetCell(realRow, aColumn);
645 if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
646 nsGkAtoms::_false, eCaseMatters)) {
647 return false;
648 }
649 }
650
651 return true;
652 }
653
654 NS_IMETHODIMP
IsEditable(int32_t aRow,nsITreeColumn * aCol,bool * _retval)655 nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol,
656 bool* _retval) {
657 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
658 NS_ENSURE_ARG(col);
659
660 ErrorResult rv;
661 *_retval = IsEditable(aRow, *col, rv);
662 return rv.StealNSResult();
663 }
664
IsSelectable(int32_t aRow,nsTreeColumn & aColumn,ErrorResult & aError)665 bool nsTreeContentView::IsSelectable(int32_t aRow, nsTreeColumn& aColumn,
666 ErrorResult& aError) {
667 if (!IsValidRowIndex(aRow)) {
668 aError.Throw(NS_ERROR_INVALID_ARG);
669 return false;
670 }
671
672 Row* row = mRows[aRow].get();
673
674 nsIContent* realRow =
675 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
676 if (realRow) {
677 Element* cell = GetCell(realRow, aColumn);
678 if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable,
679 nsGkAtoms::_false, eCaseMatters)) {
680 return false;
681 }
682 }
683
684 return true;
685 }
686
687 NS_IMETHODIMP
IsSelectable(int32_t aRow,nsITreeColumn * aCol,bool * _retval)688 nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol,
689 bool* _retval) {
690 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
691 NS_ENSURE_ARG(col);
692
693 ErrorResult rv;
694 *_retval = IsSelectable(aRow, *col, rv);
695 return rv.StealNSResult();
696 }
697
SetCellValue(int32_t aRow,nsTreeColumn & aColumn,const nsAString & aValue,ErrorResult & aError)698 void nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn& aColumn,
699 const nsAString& aValue,
700 ErrorResult& aError) {
701 if (!IsValidRowIndex(aRow)) {
702 aError.Throw(NS_ERROR_INVALID_ARG);
703 return;
704 }
705
706 Row* row = mRows[aRow].get();
707
708 nsIContent* realRow =
709 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
710 if (realRow) {
711 Element* cell = GetCell(realRow, aColumn);
712 if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
713 }
714 }
715
716 NS_IMETHODIMP
SetCellValue(int32_t aRow,nsITreeColumn * aCol,const nsAString & aValue)717 nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol,
718 const nsAString& aValue) {
719 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
720 NS_ENSURE_ARG(col);
721
722 ErrorResult rv;
723 SetCellValue(aRow, *col, aValue, rv);
724 return rv.StealNSResult();
725 }
726
SetCellText(int32_t aRow,nsTreeColumn & aColumn,const nsAString & aValue,ErrorResult & aError)727 void nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn& aColumn,
728 const nsAString& aValue,
729 ErrorResult& aError) {
730 if (!IsValidRowIndex(aRow)) {
731 aError.Throw(NS_ERROR_INVALID_ARG);
732 return;
733 }
734
735 Row* row = mRows[aRow].get();
736
737 nsIContent* realRow =
738 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
739 if (realRow) {
740 Element* cell = GetCell(realRow, aColumn);
741 if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true);
742 }
743 }
744
745 NS_IMETHODIMP
SetCellText(int32_t aRow,nsITreeColumn * aCol,const nsAString & aValue)746 nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol,
747 const nsAString& aValue) {
748 RefPtr<nsTreeColumn> col = nsTreeColumn::From(aCol);
749 NS_ENSURE_ARG(col);
750
751 ErrorResult rv;
752 SetCellText(aRow, *col, aValue, rv);
753 return rv.StealNSResult();
754 }
755
756 NS_IMETHODIMP
PerformAction(const char16_t * aAction)757 nsTreeContentView::PerformAction(const char16_t* aAction) { return NS_OK; }
758
759 NS_IMETHODIMP
PerformActionOnRow(const char16_t * aAction,int32_t aRow)760 nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow) {
761 return NS_OK;
762 }
763
764 NS_IMETHODIMP
PerformActionOnCell(const char16_t * aAction,int32_t aRow,nsITreeColumn * aCol)765 nsTreeContentView::PerformActionOnCell(const char16_t* aAction, int32_t aRow,
766 nsITreeColumn* aCol) {
767 return NS_OK;
768 }
769
GetItemAtIndex(int32_t aIndex,ErrorResult & aError)770 Element* nsTreeContentView::GetItemAtIndex(int32_t aIndex,
771 ErrorResult& aError) {
772 if (!IsValidRowIndex(aIndex)) {
773 aError.Throw(NS_ERROR_INVALID_ARG);
774 return nullptr;
775 }
776
777 return mRows[aIndex]->mContent;
778 }
779
780 NS_IMETHODIMP
GetItemAtIndex(int32_t aIndex,nsIDOMElement ** _retval)781 nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval) {
782 ErrorResult rv;
783 Element* element = GetItemAtIndex(aIndex, rv);
784 if (rv.Failed()) {
785 return rv.StealNSResult();
786 }
787
788 if (!element) {
789 *_retval = nullptr;
790 return NS_OK;
791 }
792
793 return CallQueryInterface(element, _retval);
794 }
795
GetIndexOfItem(Element * aItem)796 int32_t nsTreeContentView::GetIndexOfItem(Element* aItem) {
797 return FindContent(aItem);
798 }
799
800 NS_IMETHODIMP
GetIndexOfItem(nsIDOMElement * aItem,int32_t * _retval)801 nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) {
802 nsCOMPtr<Element> element = do_QueryInterface(aItem);
803
804 *_retval = GetIndexOfItem(element);
805
806 return NS_OK;
807 }
808
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)809 void nsTreeContentView::AttributeChanged(dom::Element* aElement,
810 int32_t aNameSpaceID,
811 nsAtom* aAttribute, int32_t aModType,
812 const nsAttrValue* aOldValue) {
813 // Lots of codepaths under here that do all sorts of stuff, so be safe.
814 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
815
816 // Make sure this notification concerns us.
817 // First check the tag to see if it's one that we care about.
818
819 if (mBoxObject && (aElement == mRoot || aElement == mBody)) {
820 mBoxObject->ClearStyleAndImageCaches();
821 mBoxObject->Invalidate();
822 }
823
824 // We don't consider non-XUL nodes.
825 nsIContent* parent = nullptr;
826 if (!aElement->IsXULElement() ||
827 ((parent = aElement->GetParent()) && !parent->IsXULElement())) {
828 return;
829 }
830 if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol, nsGkAtoms::treeitem,
831 nsGkAtoms::treeseparator,
832 nsGkAtoms::treerow, nsGkAtoms::treecell)) {
833 return;
834 }
835
836 // If we have a legal tag, go up to the tree/select and make sure
837 // that it's ours.
838
839 for (nsIContent* element = aElement; element != mBody;
840 element = element->GetParent()) {
841 if (!element) return; // this is not for us
842 if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us
843 }
844
845 // Handle changes of the hidden attribute.
846 if (aAttribute == nsGkAtoms::hidden &&
847 aElement->IsAnyOfXULElements(nsGkAtoms::treeitem,
848 nsGkAtoms::treeseparator)) {
849 bool hidden = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
850 nsGkAtoms::_true, eCaseMatters);
851
852 int32_t index = FindContent(aElement);
853 if (hidden && index >= 0) {
854 // Hide this row along with its children.
855 int32_t count = RemoveRow(index);
856 if (mBoxObject) mBoxObject->RowCountChanged(index, -count);
857 } else if (!hidden && index < 0) {
858 // Show this row along with its children.
859 nsCOMPtr<nsIContent> parent = aElement->GetParent();
860 if (parent) {
861 InsertRowFor(parent, aElement);
862 }
863 }
864
865 return;
866 }
867
868 if (aElement->IsXULElement(nsGkAtoms::treecol)) {
869 if (aAttribute == nsGkAtoms::properties) {
870 if (mBoxObject) {
871 nsCOMPtr<nsITreeColumns> cols;
872 mBoxObject->GetColumns(getter_AddRefs(cols));
873 if (cols) {
874 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aElement);
875 nsCOMPtr<nsITreeColumn> col;
876 cols->GetColumnFor(element, getter_AddRefs(col));
877 mBoxObject->InvalidateColumn(col);
878 }
879 }
880 }
881 } else if (aElement->IsXULElement(nsGkAtoms::treeitem)) {
882 int32_t index = FindContent(aElement);
883 if (index >= 0) {
884 Row* row = mRows[index].get();
885 if (aAttribute == nsGkAtoms::container) {
886 bool isContainer =
887 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
888 nsGkAtoms::_true, eCaseMatters);
889 row->SetContainer(isContainer);
890 if (mBoxObject) mBoxObject->InvalidateRow(index);
891 } else if (aAttribute == nsGkAtoms::open) {
892 bool isOpen = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
893 nsGkAtoms::_true, eCaseMatters);
894 bool wasOpen = row->IsOpen();
895 if (!isOpen && wasOpen)
896 CloseContainer(index);
897 else if (isOpen && !wasOpen)
898 OpenContainer(index);
899 } else if (aAttribute == nsGkAtoms::empty) {
900 bool isEmpty =
901 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
902 nsGkAtoms::_true, eCaseMatters);
903 row->SetEmpty(isEmpty);
904 if (mBoxObject) mBoxObject->InvalidateRow(index);
905 }
906 }
907 } else if (aElement->IsXULElement(nsGkAtoms::treeseparator)) {
908 int32_t index = FindContent(aElement);
909 if (index >= 0) {
910 if (aAttribute == nsGkAtoms::properties && mBoxObject) {
911 mBoxObject->InvalidateRow(index);
912 }
913 }
914 } else if (aElement->IsXULElement(nsGkAtoms::treerow)) {
915 if (aAttribute == nsGkAtoms::properties) {
916 nsCOMPtr<nsIContent> parent = aElement->GetParent();
917 if (parent) {
918 int32_t index = FindContent(parent);
919 if (index >= 0 && mBoxObject) {
920 mBoxObject->InvalidateRow(index);
921 }
922 }
923 }
924 } else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
925 if (aAttribute == nsGkAtoms::properties || aAttribute == nsGkAtoms::mode ||
926 aAttribute == nsGkAtoms::src || aAttribute == nsGkAtoms::value ||
927 aAttribute == nsGkAtoms::label) {
928 nsIContent* parent = aElement->GetParent();
929 if (parent) {
930 nsCOMPtr<nsIContent> grandParent = parent->GetParent();
931 if (grandParent && grandParent->IsXULElement()) {
932 int32_t index = FindContent(grandParent);
933 if (index >= 0 && mBoxObject) {
934 // XXX Should we make an effort to invalidate only cell ?
935 mBoxObject->InvalidateRow(index);
936 }
937 }
938 }
939 }
940 }
941 }
942
ContentAppended(nsIContent * aFirstNewContent)943 void nsTreeContentView::ContentAppended(nsIContent* aFirstNewContent) {
944 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
945 // Our contentinserted doesn't use the index
946 ContentInserted(cur);
947 }
948 }
949
ContentInserted(nsIContent * aChild)950 void nsTreeContentView::ContentInserted(nsIContent* aChild) {
951 NS_ASSERTION(aChild, "null ptr");
952 nsIContent* container = aChild->GetParent();
953
954 // Make sure this notification concerns us.
955 // First check the tag to see if it's one that we care about.
956
957 // Don't allow non-XUL nodes.
958 if (!aChild->IsXULElement() || !container->IsXULElement()) return;
959
960 if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator,
961 nsGkAtoms::treechildren, nsGkAtoms::treerow,
962 nsGkAtoms::treecell)) {
963 return;
964 }
965
966 // If we have a legal tag, go up to the tree/select and make sure
967 // that it's ours.
968
969 for (nsIContent* element = container; element != mBody;
970 element = element->GetParent()) {
971 if (!element) return; // this is not for us
972 if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us
973 }
974
975 // Lots of codepaths under here that do all sorts of stuff, so be safe.
976 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
977
978 if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
979 int32_t index = FindContent(container);
980 if (index >= 0) {
981 Row* row = mRows[index].get();
982 row->SetEmpty(false);
983 if (mBoxObject) mBoxObject->InvalidateRow(index);
984 if (row->IsContainer() && row->IsOpen()) {
985 int32_t count = EnsureSubtree(index);
986 if (mBoxObject) mBoxObject->RowCountChanged(index + 1, count);
987 }
988 }
989 } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
990 nsGkAtoms::treeseparator)) {
991 InsertRowFor(container, aChild);
992 } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
993 int32_t index = FindContent(container);
994 if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index);
995 } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
996 nsCOMPtr<nsIContent> parent = container->GetParent();
997 if (parent) {
998 int32_t index = FindContent(parent);
999 if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index);
1000 }
1001 }
1002 }
1003
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)1004 void nsTreeContentView::ContentRemoved(nsIContent* aChild,
1005 nsIContent* aPreviousSibling) {
1006 NS_ASSERTION(aChild, "null ptr");
1007
1008 nsIContent* container = aChild->GetParent();
1009 // Make sure this notification concerns us.
1010 // First check the tag to see if it's one that we care about.
1011
1012 // We don't consider non-XUL nodes.
1013 if (!aChild->IsXULElement() || !container->IsXULElement()) return;
1014
1015 if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator,
1016 nsGkAtoms::treechildren, nsGkAtoms::treerow,
1017 nsGkAtoms::treecell)) {
1018 return;
1019 }
1020
1021 // If we have a legal tag, go up to the tree/select and make sure
1022 // that it's ours.
1023
1024 for (nsIContent* element = container; element != mBody;
1025 element = element->GetParent()) {
1026 if (!element) return; // this is not for us
1027 if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us
1028 }
1029
1030 // Lots of codepaths under here that do all sorts of stuff, so be safe.
1031 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1032
1033 if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
1034 int32_t index = FindContent(container);
1035 if (index >= 0) {
1036 Row* row = mRows[index].get();
1037 row->SetEmpty(true);
1038 int32_t count = RemoveSubtree(index);
1039 // Invalidate also the row to update twisty.
1040 if (mBoxObject) {
1041 mBoxObject->InvalidateRow(index);
1042 mBoxObject->RowCountChanged(index + 1, -count);
1043 }
1044 }
1045 } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
1046 nsGkAtoms::treeseparator)) {
1047 int32_t index = FindContent(aChild);
1048 if (index >= 0) {
1049 int32_t count = RemoveRow(index);
1050 if (mBoxObject) mBoxObject->RowCountChanged(index, -count);
1051 }
1052 } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
1053 int32_t index = FindContent(container);
1054 if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index);
1055 } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
1056 nsCOMPtr<nsIContent> parent = container->GetParent();
1057 if (parent) {
1058 int32_t index = FindContent(parent);
1059 if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index);
1060 }
1061 }
1062 }
1063
NodeWillBeDestroyed(const nsINode * aNode)1064 void nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) {
1065 // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows?
1066 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1067 ClearRows();
1068 }
1069
1070 // Recursively serialize content, starting with aContent.
Serialize(nsIContent * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)1071 void nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex,
1072 int32_t* aIndex,
1073 nsTArray<UniquePtr<Row>>& aRows) {
1074 // Don't allow non-XUL nodes.
1075 if (!aContent->IsXULElement()) return;
1076
1077 dom::FlattenedChildIterator iter(aContent);
1078 for (nsIContent* content = iter.GetNextChild(); content;
1079 content = iter.GetNextChild()) {
1080 int32_t count = aRows.Length();
1081
1082 if (content->IsXULElement(nsGkAtoms::treeitem)) {
1083 SerializeItem(content->AsElement(), aParentIndex, aIndex, aRows);
1084 } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
1085 SerializeSeparator(content->AsElement(), aParentIndex, aIndex, aRows);
1086 }
1087
1088 *aIndex += aRows.Length() - count;
1089 }
1090 }
1091
SerializeItem(Element * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)1092 void nsTreeContentView::SerializeItem(Element* aContent, int32_t aParentIndex,
1093 int32_t* aIndex,
1094 nsTArray<UniquePtr<Row>>& aRows) {
1095 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
1096 nsGkAtoms::_true, eCaseMatters))
1097 return;
1098
1099 aRows.AppendElement(MakeUnique<Row>(aContent, aParentIndex));
1100 Row* row = aRows.LastElement().get();
1101
1102 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
1103 nsGkAtoms::_true, eCaseMatters)) {
1104 row->SetContainer(true);
1105 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1106 nsGkAtoms::_true, eCaseMatters)) {
1107 row->SetOpen(true);
1108 nsIContent* child =
1109 nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren);
1110 if (child && child->IsXULElement()) {
1111 // Now, recursively serialize our child.
1112 int32_t count = aRows.Length();
1113 int32_t index = 0;
1114 Serialize(child, aParentIndex + *aIndex + 1, &index, aRows);
1115 row->mSubtreeSize += aRows.Length() - count;
1116 } else
1117 row->SetEmpty(true);
1118 } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
1119 nsGkAtoms::_true, eCaseMatters)) {
1120 row->SetEmpty(true);
1121 }
1122 }
1123 }
1124
SerializeSeparator(Element * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)1125 void nsTreeContentView::SerializeSeparator(Element* aContent,
1126 int32_t aParentIndex,
1127 int32_t* aIndex,
1128 nsTArray<UniquePtr<Row>>& aRows) {
1129 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
1130 nsGkAtoms::_true, eCaseMatters))
1131 return;
1132
1133 auto row = MakeUnique<Row>(aContent, aParentIndex);
1134 row->SetSeparator(true);
1135 aRows.AppendElement(Move(row));
1136 }
1137
GetIndexInSubtree(nsIContent * aContainer,nsIContent * aContent,int32_t * aIndex)1138 void nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer,
1139 nsIContent* aContent,
1140 int32_t* aIndex) {
1141 if (!aContainer->IsXULElement()) return;
1142
1143 for (nsIContent* content = aContainer->GetFirstChild(); content;
1144 content = content->GetNextSibling()) {
1145 if (content == aContent) break;
1146
1147 if (content->IsXULElement(nsGkAtoms::treeitem)) {
1148 if (!content->AsElement()->AttrValueIs(kNameSpaceID_None,
1149 nsGkAtoms::hidden,
1150 nsGkAtoms::_true, eCaseMatters)) {
1151 (*aIndex)++;
1152 if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
1153 nsGkAtoms::container,
1154 nsGkAtoms::_true, eCaseMatters) &&
1155 content->AsElement()->AttrValueIs(kNameSpaceID_None,
1156 nsGkAtoms::open, nsGkAtoms::_true,
1157 eCaseMatters)) {
1158 nsIContent* child =
1159 nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren);
1160 if (child && child->IsXULElement())
1161 GetIndexInSubtree(child, aContent, aIndex);
1162 }
1163 }
1164 } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
1165 if (!content->AsElement()->AttrValueIs(kNameSpaceID_None,
1166 nsGkAtoms::hidden,
1167 nsGkAtoms::_true, eCaseMatters))
1168 (*aIndex)++;
1169 }
1170 }
1171 }
1172
EnsureSubtree(int32_t aIndex)1173 int32_t nsTreeContentView::EnsureSubtree(int32_t aIndex) {
1174 Row* row = mRows[aIndex].get();
1175
1176 nsIContent* child;
1177 child =
1178 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren);
1179 if (!child || !child->IsXULElement()) {
1180 return 0;
1181 }
1182
1183 AutoTArray<UniquePtr<Row>, 8> rows;
1184 int32_t index = 0;
1185 Serialize(child, aIndex, &index, rows);
1186 // Insert |rows| into |mRows| at position |aIndex|, by first creating empty
1187 // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note
1188 // that we can't simply use InsertElementsAt with an array argument, since
1189 // the destination can't steal ownership from its const source argument.)
1190 UniquePtr<Row>* newRows = mRows.InsertElementsAt(aIndex + 1, rows.Length());
1191 for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) {
1192 newRows[i] = Move(rows[i]);
1193 }
1194 int32_t count = rows.Length();
1195
1196 row->mSubtreeSize += count;
1197 UpdateSubtreeSizes(row->mParentIndex, count);
1198
1199 // Update parent indexes, but skip newly added rows.
1200 // They already have correct values.
1201 UpdateParentIndexes(aIndex, count + 1, count);
1202
1203 return count;
1204 }
1205
RemoveSubtree(int32_t aIndex)1206 int32_t nsTreeContentView::RemoveSubtree(int32_t aIndex) {
1207 Row* row = mRows[aIndex].get();
1208 int32_t count = row->mSubtreeSize;
1209
1210 mRows.RemoveElementsAt(aIndex + 1, count);
1211
1212 row->mSubtreeSize -= count;
1213 UpdateSubtreeSizes(row->mParentIndex, -count);
1214
1215 UpdateParentIndexes(aIndex, 0, -count);
1216
1217 return count;
1218 }
1219
InsertRowFor(nsIContent * aParent,nsIContent * aChild)1220 void nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) {
1221 int32_t grandParentIndex = -1;
1222 bool insertRow = false;
1223
1224 nsCOMPtr<nsIContent> grandParent = aParent->GetParent();
1225
1226 if (grandParent->IsXULElement(nsGkAtoms::tree)) {
1227 // Allow insertion to the outermost container.
1228 insertRow = true;
1229 } else {
1230 // Test insertion to an inner container.
1231
1232 // First try to find this parent in our array of rows, if we find one
1233 // we can be sure that all other parents are open too.
1234 grandParentIndex = FindContent(grandParent);
1235 if (grandParentIndex >= 0) {
1236 // Got it, now test if it is open.
1237 if (mRows[grandParentIndex]->IsOpen()) insertRow = true;
1238 }
1239 }
1240
1241 if (insertRow) {
1242 int32_t index = 0;
1243 GetIndexInSubtree(aParent, aChild, &index);
1244
1245 int32_t count = InsertRow(grandParentIndex, index, aChild);
1246 if (mBoxObject)
1247 mBoxObject->RowCountChanged(grandParentIndex + index + 1, count);
1248 }
1249 }
1250
InsertRow(int32_t aParentIndex,int32_t aIndex,nsIContent * aContent)1251 int32_t nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex,
1252 nsIContent* aContent) {
1253 AutoTArray<UniquePtr<Row>, 8> rows;
1254 if (aContent->IsXULElement(nsGkAtoms::treeitem)) {
1255 SerializeItem(aContent->AsElement(), aParentIndex, &aIndex, rows);
1256 } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) {
1257 SerializeSeparator(aContent->AsElement(), aParentIndex, &aIndex, rows);
1258 }
1259
1260 // We can't use InsertElementsAt since the destination can't steal
1261 // ownership from its const source argument.
1262 int32_t count = rows.Length();
1263 for (nsTArray<Row>::index_type i = 0; i < size_t(count); i++) {
1264 mRows.InsertElementAt(aParentIndex + aIndex + i + 1, Move(rows[i]));
1265 }
1266
1267 UpdateSubtreeSizes(aParentIndex, count);
1268
1269 // Update parent indexes, but skip added rows.
1270 // They already have correct values.
1271 UpdateParentIndexes(aParentIndex + aIndex, count + 1, count);
1272
1273 return count;
1274 }
1275
RemoveRow(int32_t aIndex)1276 int32_t nsTreeContentView::RemoveRow(int32_t aIndex) {
1277 Row* row = mRows[aIndex].get();
1278 int32_t count = row->mSubtreeSize + 1;
1279 int32_t parentIndex = row->mParentIndex;
1280
1281 mRows.RemoveElementsAt(aIndex, count);
1282
1283 UpdateSubtreeSizes(parentIndex, -count);
1284
1285 UpdateParentIndexes(aIndex, 0, -count);
1286
1287 return count;
1288 }
1289
ClearRows()1290 void nsTreeContentView::ClearRows() {
1291 mRows.Clear();
1292 mRoot = nullptr;
1293 mBody = nullptr;
1294 // Remove ourselves from mDocument's observers.
1295 if (mDocument) {
1296 mDocument->RemoveObserver(this);
1297 mDocument = nullptr;
1298 }
1299 }
1300
OpenContainer(int32_t aIndex)1301 void nsTreeContentView::OpenContainer(int32_t aIndex) {
1302 Row* row = mRows[aIndex].get();
1303 row->SetOpen(true);
1304
1305 int32_t count = EnsureSubtree(aIndex);
1306 if (mBoxObject) {
1307 mBoxObject->InvalidateRow(aIndex);
1308 mBoxObject->RowCountChanged(aIndex + 1, count);
1309 }
1310 }
1311
CloseContainer(int32_t aIndex)1312 void nsTreeContentView::CloseContainer(int32_t aIndex) {
1313 Row* row = mRows[aIndex].get();
1314 row->SetOpen(false);
1315
1316 int32_t count = RemoveSubtree(aIndex);
1317 if (mBoxObject) {
1318 mBoxObject->InvalidateRow(aIndex);
1319 mBoxObject->RowCountChanged(aIndex + 1, -count);
1320 }
1321 }
1322
FindContent(nsIContent * aContent)1323 int32_t nsTreeContentView::FindContent(nsIContent* aContent) {
1324 for (uint32_t i = 0; i < mRows.Length(); i++) {
1325 if (mRows[i]->mContent == aContent) {
1326 return i;
1327 }
1328 }
1329
1330 return -1;
1331 }
1332
UpdateSubtreeSizes(int32_t aParentIndex,int32_t count)1333 void nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex,
1334 int32_t count) {
1335 while (aParentIndex >= 0) {
1336 Row* row = mRows[aParentIndex].get();
1337 row->mSubtreeSize += count;
1338 aParentIndex = row->mParentIndex;
1339 }
1340 }
1341
UpdateParentIndexes(int32_t aIndex,int32_t aSkip,int32_t aCount)1342 void nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip,
1343 int32_t aCount) {
1344 int32_t count = mRows.Length();
1345 for (int32_t i = aIndex + aSkip; i < count; i++) {
1346 Row* row = mRows[i].get();
1347 if (row->mParentIndex > aIndex) {
1348 row->mParentIndex += aCount;
1349 }
1350 }
1351 }
1352
GetCell(nsIContent * aContainer,nsTreeColumn & aCol)1353 Element* nsTreeContentView::GetCell(nsIContent* aContainer,
1354 nsTreeColumn& aCol) {
1355 int32_t colIndex(aCol.GetIndex());
1356
1357 // Traverse through cells, try to find the cell by index in a row.
1358 Element* result = nullptr;
1359 int32_t j = 0;
1360 dom::FlattenedChildIterator iter(aContainer);
1361 for (nsIContent* cell = iter.GetNextChild(); cell;
1362 cell = iter.GetNextChild()) {
1363 if (cell->IsXULElement(nsGkAtoms::treecell)) {
1364 if (j == colIndex) {
1365 result = cell->AsElement();
1366 break;
1367 }
1368 j++;
1369 }
1370 }
1371
1372 return result;
1373 }
1374
IsValidRowIndex(int32_t aRowIndex)1375 bool nsTreeContentView::IsValidRowIndex(int32_t aRowIndex) {
1376 return aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length());
1377 }
1378