Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/xul/nsXULSortService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
/*
7
  This file provides the implementation for the sort service manager.
8
 */
9
10
#include "nsCOMArray.h"
11
#include "nsCOMPtr.h"
12
#include "nsIContent.h"
13
#include "nsIServiceManager.h"
14
#include "nsGkAtoms.h"
15
#include "nsNameSpaceManager.h"
16
#include "nsXULContentUtils.h"
17
#include "nsString.h"
18
#include "nsQuickSort.h"
19
#include "nsWhitespaceTokenizer.h"
20
#include "nsXULSortService.h"
21
#include "nsXULElement.h"
22
#include "nsICollation.h"
23
#include "nsTArray.h"
24
#include "nsUnicharUtils.h"
25
26
#include "mozilla/dom/Element.h"
27
28
const unsigned long SORT_COMPARECASE = 0x0001;
29
const unsigned long SORT_INTEGER = 0x0100;
30
31
enum nsSortState_direction {
32
  nsSortState_descending,
33
  nsSortState_ascending,
34
  nsSortState_natural
35
};
36
37
// the sort state holds info about the current sort
38
struct MOZ_STACK_CLASS nsSortState final
39
{
40
  bool initialized;
41
  MOZ_INIT_OUTSIDE_CTOR bool invertSort;
42
43
  uint32_t sortHints;
44
45
  MOZ_INIT_OUTSIDE_CTOR nsSortState_direction direction;
46
  nsAutoString sort;
47
  nsTArray<RefPtr<nsAtom>> sortKeys;
48
49
  nsCOMPtr<nsIContent> lastContainer;
50
  MOZ_INIT_OUTSIDE_CTOR bool lastWasFirst, lastWasLast;
51
52
  nsSortState()
53
    : initialized(false),
54
      sortHints(0)
55
0
  {
56
0
  }
57
};
58
59
// information about a particular item to be sorted
60
struct contentSortInfo {
61
  nsCOMPtr<nsIContent> content;
62
  nsCOMPtr<nsIContent> parent;
63
  void swap(contentSortInfo& other)
64
0
  {
65
0
    content.swap(other.content);
66
0
    parent.swap(other.parent);
67
0
  }
68
};
69
70
/**
71
 * Set sortActive and sortDirection attributes on a tree column when a sort
72
 * is done. The column to change is the one with a sort attribute that
73
 * matches the sort key. The sort attributes are removed from the other
74
 * columns.
75
 */
76
static void
77
SetSortColumnHints(nsIContent *content,
78
                   const nsAString &sortResource,
79
                   const nsAString &sortDirection)
80
0
{
81
0
  // set sort info on current column. This ensures that the
82
0
  // column header sort indicator is updated properly.
83
0
  for (nsIContent* child = content->GetFirstChild();
84
0
       child;
85
0
       child = child->GetNextSibling()) {
86
0
    if (child->IsXULElement(nsGkAtoms::treecols)) {
87
0
      SetSortColumnHints(child, sortResource, sortDirection);
88
0
    } else if (child->IsXULElement(nsGkAtoms::treecol)) {
89
0
      nsAutoString value;
90
0
      child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value);
91
0
      if (value == sortResource) {
92
0
        child->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
93
0
                                    NS_LITERAL_STRING("true"), true);
94
0
95
0
        child->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
96
0
                                    sortDirection, true);
97
0
        // Note: don't break out of loop; want to set/unset
98
0
        // attribs on ALL sort columns
99
0
      } else if (!value.IsEmpty()) {
100
0
        child->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
101
0
                                      true);
102
0
        child->AsElement()->UnsetAttr(kNameSpaceID_None,
103
0
                                      nsGkAtoms::sortDirection, true);
104
0
      }
105
0
    }
106
0
  }
107
0
}
108
109
/**
110
 * Set sort and sortDirection attributes when a sort is done.
111
 */
112
static void
113
SetSortHints(Element* aElement, nsSortState* aSortState)
114
0
{
115
0
  // set sort and sortDirection attributes when is sort is done
116
0
  aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::sort,
117
0
                    aSortState->sort, true);
118
0
119
0
  nsAutoString direction;
120
0
  if (aSortState->direction == nsSortState_descending)
121
0
    direction.AssignLiteral("descending");
122
0
  else if (aSortState->direction == nsSortState_ascending)
123
0
    direction.AssignLiteral("ascending");
124
0
  aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
125
0
                    direction, true);
126
0
127
0
  // for trees, also set the sort info on the currently sorted column
128
0
  if (aElement->IsXULElement(nsGkAtoms::tree)) {
129
0
    if (aSortState->sortKeys.Length() >= 1) {
130
0
      nsAutoString sortkey;
131
0
      aSortState->sortKeys[0]->ToString(sortkey);
132
0
      SetSortColumnHints(aElement, sortkey, direction);
133
0
    }
134
0
  }
135
0
}
136
137
/**
138
 * Determine the list of items which need to be sorted. This is determined
139
 * in the following way:
140
 *   - for elements that have a content builder, get its list of generated
141
 *     results
142
 *   - otherwise, for trees, get the child treeitems
143
 *   - otherwise, get the direct children
144
 */
145
static nsresult
146
GetItemsToSort(nsIContent *aContainer,
147
               nsSortState* aSortState,
148
               nsTArray<contentSortInfo>& aSortItems)
149
0
{
150
0
  // Get the children. For trees, get the treechildren element and
151
0
  // use that as the parent
152
0
  RefPtr<Element> treechildren;
153
0
  if (aContainer->IsXULElement(nsGkAtoms::tree)) {
154
0
    nsXULContentUtils::FindChildByTag(aContainer,
155
0
                                      kNameSpaceID_XUL,
156
0
                                      nsGkAtoms::treechildren,
157
0
                                      getter_AddRefs(treechildren));
158
0
    if (!treechildren)
159
0
      return NS_OK;
160
0
161
0
    aContainer = treechildren;
162
0
  }
163
0
164
0
  for (nsIContent* child = aContainer->GetFirstChild();
165
0
       child;
166
0
       child = child->GetNextSibling()) {
167
0
    contentSortInfo* cinfo = aSortItems.AppendElement();
168
0
    if (!cinfo)
169
0
      return NS_ERROR_OUT_OF_MEMORY;
170
0
171
0
    cinfo->content = child;
172
0
  }
173
0
174
0
  return NS_OK;
175
0
}
176
177
/**
178
 * Compares aLeft and aRight and returns < 0, 0, or > 0. The sort
179
 * hints are checked for case matching and integer sorting.
180
 */
181
static int32_t
182
CompareValues(const nsAString& aLeft,
183
              const nsAString& aRight,
184
              uint32_t aSortHints)
185
0
{
186
0
  if (aSortHints & SORT_INTEGER) {
187
0
    nsresult err;
188
0
    int32_t leftint = PromiseFlatString(aLeft).ToInteger(&err);
189
0
    if (NS_SUCCEEDED(err)) {
190
0
      int32_t rightint = PromiseFlatString(aRight).ToInteger(&err);
191
0
      if (NS_SUCCEEDED(err)) {
192
0
        return leftint - rightint;
193
0
      }
194
0
    }
195
0
    // if they aren't integers, just fall through and compare strings
196
0
  }
197
0
198
0
  if (aSortHints & SORT_COMPARECASE) {
199
0
    return ::Compare(aLeft, aRight);
200
0
  }
201
0
202
0
  nsICollation* collation = nsXULContentUtils::GetCollation();
203
0
  if (collation) {
204
0
    int32_t result;
205
0
    collation->CompareString(nsICollation::kCollationCaseInSensitive,
206
0
                             aLeft, aRight, &result);
207
0
    return result;
208
0
  }
209
0
210
0
  return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator());
211
0
}
212
213
static int
214
testSortCallback(const void *data1, const void *data2, void *privateData)
215
0
{
216
0
  /// Note: testSortCallback is a small C callback stub for NS_QuickSort
217
0
  contentSortInfo *left = (contentSortInfo *)data1;
218
0
  contentSortInfo *right = (contentSortInfo *)data2;
219
0
  nsSortState* sortState = (nsSortState *)privateData;
220
0
221
0
  int32_t sortOrder = 0;
222
0
223
0
  int32_t length = sortState->sortKeys.Length();
224
0
  for (int32_t t = 0; t < length; t++) {
225
0
    // compare attributes. Ignore namespaces for now.
226
0
    nsAutoString leftstr, rightstr;
227
0
    if (left->content->IsElement()) {
228
0
      left->content->AsElement()->GetAttr(kNameSpaceID_None,
229
0
                                          sortState->sortKeys[t],
230
0
                                          leftstr);
231
0
    }
232
0
    if (right->content->IsElement()) {
233
0
      right->content->AsElement()->GetAttr(kNameSpaceID_None,
234
0
                                           sortState->sortKeys[t], rightstr);
235
0
    }
236
0
237
0
    sortOrder = CompareValues(leftstr, rightstr, sortState->sortHints);
238
0
  }
239
0
240
0
  if (sortState->direction == nsSortState_descending)
241
0
    sortOrder = -sortOrder;
242
0
243
0
  return sortOrder;
244
0
}
245
246
/**
247
 * Given a list of sortable items, reverse the list. This is done
248
 * when simply changing the sort direction for the same key.
249
 */
250
static nsresult
251
InvertSortInfo(nsTArray<contentSortInfo>& aData,
252
               int32_t aStart, int32_t aNumItems)
253
0
{
254
0
  if (aNumItems > 1) {
255
0
    // reverse the items in the array starting from aStart
256
0
    int32_t upPoint = (aNumItems + 1) / 2 + aStart;
257
0
    int32_t downPoint = (aNumItems - 2) / 2 + aStart;
258
0
    int32_t half = aNumItems / 2;
259
0
    while (half-- > 0) {
260
0
      aData[downPoint--].swap(aData[upPoint++]);
261
0
    }
262
0
  }
263
0
  return NS_OK;
264
0
}
265
266
/**
267
 * Sort a container using the supplied sort state details.
268
 */
269
static nsresult
270
SortContainer(nsIContent *aContainer, nsSortState* aSortState)
271
0
{
272
0
  nsTArray<contentSortInfo> items;
273
0
  nsresult rv = GetItemsToSort(aContainer, aSortState, items);
274
0
  NS_ENSURE_SUCCESS(rv, rv);
275
0
276
0
  uint32_t numResults = items.Length();
277
0
  if (!numResults)
278
0
    return NS_OK;
279
0
280
0
  uint32_t i;
281
0
282
0
  // if the items are just being inverted, that is, just switching between
283
0
  // ascending and descending, just reverse the list.
284
0
  if (aSortState->invertSort)
285
0
    InvertSortInfo(items, 0, numResults);
286
0
  else
287
0
    NS_QuickSort((void *)items.Elements(), numResults,
288
0
                 sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
289
0
290
0
  // first remove the items from the old positions
291
0
  for (i = 0; i < numResults; i++) {
292
0
    nsIContent* child = items[i].content;
293
0
    nsIContent* parent = child->GetParent();
294
0
295
0
    if (parent) {
296
0
      // remember the parent so that it can be reinserted back
297
0
      // into the same parent. This is necessary as multiple rules
298
0
      // may generate results which get placed in different locations.
299
0
      items[i].parent = parent;
300
0
      parent->RemoveChildNode(child, true);
301
0
    }
302
0
  }
303
0
304
0
  // now add the items back in sorted order
305
0
  for (i = 0; i < numResults; i++)
306
0
  {
307
0
    nsIContent* child = items[i].content;
308
0
    nsIContent* parent = items[i].parent;
309
0
    if (parent) {
310
0
      parent->AppendChildTo(child, true);
311
0
312
0
      // if it's a container in a tree or menu, find its children,
313
0
      // and sort those also
314
0
      if (!child->IsElement() ||
315
0
          !child->AsElement()->AttrValueIs(kNameSpaceID_None,
316
0
                                           nsGkAtoms::container,
317
0
                                           nsGkAtoms::_true, eCaseMatters))
318
0
        continue;
319
0
320
0
      for (nsIContent* grandchild = child->GetFirstChild();
321
0
           grandchild;
322
0
           grandchild = grandchild->GetNextSibling()) {
323
0
        mozilla::dom::NodeInfo *ni = grandchild->NodeInfo();
324
0
        nsAtom *localName = ni->NameAtom();
325
0
        if (ni->NamespaceID() == kNameSpaceID_XUL &&
326
0
            (localName == nsGkAtoms::treechildren ||
327
0
             localName == nsGkAtoms::menupopup)) {
328
0
          SortContainer(grandchild, aSortState);
329
0
        }
330
0
      }
331
0
    }
332
0
  }
333
0
334
0
  return NS_OK;
335
0
}
336
337
/**
338
 * Initialize sort information from attributes specified on the container,
339
 * the sort key and sort direction.
340
 *
341
 * @param aRootElement the element that contains sort attributes
342
 * @param aContainer the container to sort, usually equal to aRootElement
343
 * @param aSortKey space separated list of sort keys
344
 * @param aSortDirection direction to sort in
345
 * @param aSortState structure filled in with sort data
346
 */
347
static nsresult
348
InitializeSortState(Element* aRootElement,
349
                    Element* aContainer,
350
                    const nsAString& aSortKey,
351
                    const nsAString& aSortHints,
352
                    nsSortState* aSortState)
353
0
{
354
0
  // used as an optimization for the content builder
355
0
  if (aContainer != aSortState->lastContainer.get()) {
356
0
    aSortState->lastContainer = aContainer;
357
0
    aSortState->lastWasFirst = false;
358
0
    aSortState->lastWasLast = false;
359
0
  }
360
0
361
0
  // The sort attribute is of the form: sort="key1 key2 ..."
362
0
  nsAutoString sort(aSortKey);
363
0
  aSortState->sortKeys.Clear();
364
0
  nsWhitespaceTokenizer tokenizer(sort);
365
0
  while (tokenizer.hasMoreTokens()) {
366
0
    RefPtr<nsAtom> keyatom = NS_Atomize(tokenizer.nextToken());
367
0
    NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
368
0
    aSortState->sortKeys.AppendElement(keyatom);
369
0
  }
370
0
371
0
  aSortState->sort.Assign(sort);
372
0
  aSortState->direction = nsSortState_natural;
373
0
374
0
  bool noNaturalState = false;
375
0
  nsWhitespaceTokenizer hintsTokenizer(aSortHints);
376
0
  while (hintsTokenizer.hasMoreTokens()) {
377
0
    const nsDependentSubstring& token(hintsTokenizer.nextToken());
378
0
    if (token.EqualsLiteral("comparecase"))
379
0
      aSortState->sortHints |= SORT_COMPARECASE;
380
0
    else if (token.EqualsLiteral("integer"))
381
0
      aSortState->sortHints |= SORT_INTEGER;
382
0
    else if (token.EqualsLiteral("descending"))
383
0
      aSortState->direction = nsSortState_descending;
384
0
    else if (token.EqualsLiteral("ascending"))
385
0
      aSortState->direction = nsSortState_ascending;
386
0
    else if (token.EqualsLiteral("twostate"))
387
0
      noNaturalState = true;
388
0
  }
389
0
390
0
  // if the twostate flag was set, the natural order is skipped and only
391
0
  // ascending and descending are allowed
392
0
  if (aSortState->direction == nsSortState_natural && noNaturalState) {
393
0
    aSortState->direction = nsSortState_ascending;
394
0
  }
395
0
396
0
  // set up sort order info
397
0
  aSortState->invertSort = false;
398
0
399
0
  nsAutoString existingsort;
400
0
  aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort);
401
0
  nsAutoString existingsortDirection;
402
0
  aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, existingsortDirection);
403
0
404
0
  // if just switching direction, set the invertSort flag
405
0
  if (sort.Equals(existingsort)) {
406
0
    if (aSortState->direction == nsSortState_descending) {
407
0
      if (existingsortDirection.EqualsLiteral("ascending"))
408
0
        aSortState->invertSort = true;
409
0
    }
410
0
    else if (aSortState->direction == nsSortState_ascending &&
411
0
             existingsortDirection.EqualsLiteral("descending")) {
412
0
      aSortState->invertSort = true;
413
0
    }
414
0
  }
415
0
416
0
  aSortState->initialized = true;
417
0
418
0
  return NS_OK;
419
0
}
420
421
nsresult
422
mozilla::XULWidgetSort(Element* aNode,
423
                       const nsAString& aSortKey,
424
                       const nsAString& aSortHints)
425
0
{
426
0
  nsSortState sortState;
427
0
  nsresult rv = InitializeSortState(aNode, aNode,
428
0
                                    aSortKey, aSortHints, &sortState);
429
0
  NS_ENSURE_SUCCESS(rv, rv);
430
0
431
0
  // store sort info in attributes on content
432
0
  SetSortHints(aNode, &sortState);
433
0
  rv = SortContainer(aNode, &sortState);
434
0
435
0
  return rv;
436
0
}