Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/HTMLTableEditor.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
#include <stdio.h>
7
8
#include "mozilla/HTMLEditor.h"
9
10
#include "HTMLEditUtils.h"
11
#include "mozilla/Assertions.h"
12
#include "mozilla/EditAction.h"
13
#include "mozilla/EditorDOMPoint.h"
14
#include "mozilla/EditorUtils.h"
15
#include "mozilla/FlushType.h"
16
#include "mozilla/dom/Selection.h"
17
#include "mozilla/dom/Element.h"
18
#include "nsAString.h"
19
#include "nsAlgorithm.h"
20
#include "nsCOMPtr.h"
21
#include "nsDebug.h"
22
#include "nsError.h"
23
#include "nsFrameSelection.h"
24
#include "nsGkAtoms.h"
25
#include "nsAtom.h"
26
#include "nsIContent.h"
27
#include "nsIFrame.h"
28
#include "nsINode.h"
29
#include "nsIPresShell.h"
30
#include "nsISupportsUtils.h"
31
#include "nsITableCellLayout.h" // For efficient access to table cell
32
#include "nsLiteralString.h"
33
#include "nsQueryFrame.h"
34
#include "nsRange.h"
35
#include "nsString.h"
36
#include "nsTArray.h"
37
#include "nsTableCellFrame.h"
38
#include "nsTableWrapperFrame.h"
39
#include "nscore.h"
40
#include <algorithm>
41
42
namespace mozilla {
43
44
using namespace dom;
45
46
/**
47
 * Stack based helper class for restoring selection after table edit.
48
 */
49
class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final
50
{
51
private:
52
  RefPtr<HTMLEditor> mHTMLEditor;
53
  RefPtr<Element> mTable;
54
  int32_t mCol, mRow, mDirection, mSelected;
55
56
public:
57
  AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor,
58
                                    Element* aTable,
59
                                    int32_t aRow,
60
                                    int32_t aCol,
61
                                    int32_t aDirection,
62
                                    bool aSelected)
63
    : mHTMLEditor(&aHTMLEditor)
64
    , mTable(aTable)
65
    , mCol(aCol)
66
    , mRow(aRow)
67
    , mDirection(aDirection)
68
    , mSelected(aSelected)
69
0
  {
70
0
  }
71
72
  ~AutoSelectionSetterAfterTableEdit()
73
0
  {
74
0
    if (mHTMLEditor) {
75
0
      mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
76
0
                                              mSelected);
77
0
    }
78
0
  }
79
80
  // This is needed to abort the caret reset in the destructor
81
  //  when one method yields control to another
82
  void CancelSetCaret()
83
0
  {
84
0
    mHTMLEditor = nullptr;
85
0
    mTable = nullptr;
86
0
  }
87
};
88
89
nsresult
90
HTMLEditor::InsertCell(Element* aCell,
91
                       int32_t aRowSpan,
92
                       int32_t aColSpan,
93
                       bool aAfter,
94
                       bool aIsHeader,
95
                       Element** aNewCell)
96
0
{
97
0
  if (aNewCell) {
98
0
    *aNewCell = nullptr;
99
0
  }
100
0
101
0
  if (NS_WARN_IF(!aCell)) {
102
0
    return NS_ERROR_NULL_POINTER;
103
0
  }
104
0
105
0
  // And the parent and offsets needed to do an insert
106
0
  EditorDOMPoint pointToInsert(aCell);
107
0
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
108
0
    return NS_ERROR_INVALID_ARG;
109
0
  }
110
0
111
0
  RefPtr<Element> newCell =
112
0
    CreateElementWithDefaults(aIsHeader ? *nsGkAtoms::th : *nsGkAtoms::td);
113
0
  if (NS_WARN_IF(!newCell)) {
114
0
    return NS_ERROR_FAILURE;
115
0
  }
116
0
117
0
  //Optional: return new cell created
118
0
  if (aNewCell) {
119
0
    *aNewCell = do_AddRef(newCell).take();
120
0
  }
121
0
122
0
  if (aRowSpan > 1) {
123
0
    // Note: Do NOT use editor transaction for this
124
0
    nsAutoString newRowSpan;
125
0
    newRowSpan.AppendInt(aRowSpan, 10);
126
0
    newCell->SetAttr(kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
127
0
  }
128
0
  if (aColSpan > 1) {
129
0
    // Note: Do NOT use editor transaction for this
130
0
    nsAutoString newColSpan;
131
0
    newColSpan.AppendInt(aColSpan, 10);
132
0
    newCell->SetAttr(kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
133
0
  }
134
0
  if (aAfter) {
135
0
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
136
0
    NS_WARNING_ASSERTION(advanced,
137
0
      "Failed to advance offset to after the old cell");
138
0
  }
139
0
140
0
  // Don't let Rules System change the selection.
141
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
142
0
  return InsertNodeWithTransaction(*newCell, pointToInsert);
143
0
}
144
145
nsresult
146
HTMLEditor::SetColSpan(Element* aCell,
147
                       int32_t aColSpan)
148
0
{
149
0
  if (NS_WARN_IF(!aCell)) {
150
0
    return NS_ERROR_INVALID_ARG;
151
0
  }
152
0
  nsAutoString newSpan;
153
0
  newSpan.AppendInt(aColSpan, 10);
154
0
  return SetAttributeWithTransaction(*aCell, *nsGkAtoms::colspan, newSpan);
155
0
}
156
157
nsresult
158
HTMLEditor::SetRowSpan(Element* aCell,
159
                       int32_t aRowSpan)
160
0
{
161
0
  if (NS_WARN_IF(!aCell)) {
162
0
    return NS_ERROR_INVALID_ARG;
163
0
  }
164
0
  nsAutoString newSpan;
165
0
  newSpan.AppendInt(aRowSpan, 10);
166
0
  return SetAttributeWithTransaction(*aCell, *nsGkAtoms::rowspan, newSpan);
167
0
}
168
169
NS_IMETHODIMP
170
HTMLEditor::InsertTableCell(int32_t aNumberOfCellsToInsert,
171
                            bool aInsertAfterSelectedCell)
172
0
{
173
0
  nsresult rv =
174
0
    InsertTableCellsWithTransaction(aNumberOfCellsToInsert,
175
0
      aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell :
176
0
                                 InsertPosition::eBeforeSelectedCell);
177
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
178
0
    return NS_ERROR_FAILURE;
179
0
  }
180
0
  return NS_OK;
181
0
}
182
183
nsresult
184
HTMLEditor::InsertTableCellsWithTransaction(int32_t aNumberOfCellsToInsert,
185
                                            InsertPosition aInsertPosition)
186
0
{
187
0
  RefPtr<Element> table;
188
0
  RefPtr<Element> curCell;
189
0
  nsCOMPtr<nsINode> cellParent;
190
0
  int32_t cellOffset, startRowIndex, startColIndex;
191
0
  nsresult rv = GetCellContext(nullptr,
192
0
                               getter_AddRefs(table),
193
0
                               getter_AddRefs(curCell),
194
0
                               getter_AddRefs(cellParent), &cellOffset,
195
0
                               &startRowIndex, &startColIndex);
196
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
197
0
    return rv;
198
0
  }
199
0
  if (NS_WARN_IF(!curCell)) {
200
0
    // Don't fail if no cell found.
201
0
    return NS_OK;
202
0
  }
203
0
204
0
  // Get more data for current cell in row we are inserting at since we need
205
0
  // colspan value.
206
0
  int32_t curStartRowIndex = 0, curStartColIndex = 0;
207
0
  int32_t rowSpan = 0, colSpan = 0;
208
0
  int32_t actualRowSpan = 0, actualColSpan = 0;
209
0
  bool isSelected = false;
210
0
  rv = GetCellDataAt(table, startRowIndex, startColIndex,
211
0
                     getter_AddRefs(curCell),
212
0
                     &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
213
0
                     &actualRowSpan, &actualColSpan, &isSelected);
214
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
215
0
    return rv;
216
0
  }
217
0
  if (NS_WARN_IF(!curCell)) {
218
0
    return NS_ERROR_FAILURE;
219
0
  }
220
0
221
0
  int32_t newCellIndex;
222
0
  switch (aInsertPosition) {
223
0
    case InsertPosition::eBeforeSelectedCell:
224
0
      newCellIndex = startColIndex;
225
0
      break;
226
0
    case InsertPosition::eAfterSelectedCell:
227
0
      newCellIndex = startColIndex + colSpan;
228
0
      break;
229
0
    default:
230
0
      MOZ_ASSERT_UNREACHABLE("Invalid InsertPosition");
231
0
  }
232
0
233
0
  // We control selection resetting after the insert.
234
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
235
0
                                             newCellIndex, ePreviousColumn,
236
0
                                             false);
237
0
  // So, suppress Rules System selection munging.
238
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
239
0
240
0
  EditorDOMPoint pointToInsert(cellParent, cellOffset);
241
0
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
242
0
    return NS_ERROR_FAILURE;
243
0
  }
244
0
  if (aInsertPosition == InsertPosition::eAfterSelectedCell) {
245
0
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
246
0
    NS_WARNING_ASSERTION(advanced,
247
0
                         "Faild to move insertion point after the cell");
248
0
  }
249
0
  for (int32_t i = 0; i < aNumberOfCellsToInsert; i++) {
250
0
    RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
251
0
    if (NS_WARN_IF(!newCell)) {
252
0
      return NS_ERROR_FAILURE;
253
0
    }
254
0
    AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
255
0
    rv = InsertNodeWithTransaction(*newCell, pointToInsert);
256
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
257
0
      return rv;
258
0
    }
259
0
  }
260
0
  return NS_OK;
261
0
}
262
263
NS_IMETHODIMP
264
HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
265
                        Element** aFirstRowElement)
266
0
{
267
0
  if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
268
0
    return NS_ERROR_INVALID_ARG;
269
0
  }
270
0
  ErrorResult error;
271
0
  RefPtr<Element> firstRowElement =
272
0
    GetFirstTableRowElement(*aTableOrElementInTable, error);
273
0
  if (NS_WARN_IF(error.Failed())) {
274
0
    return error.StealNSResult();
275
0
  }
276
0
  firstRowElement.forget(aFirstRowElement);
277
0
  return NS_OK;
278
0
}
279
280
Element*
281
HTMLEditor::GetFirstTableRowElement(Element& aTableOrElementInTable,
282
                                    ErrorResult& aRv) const
283
0
{
284
0
  MOZ_ASSERT(!aRv.Failed());
285
0
286
0
  Element* tableElement =
287
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::table,
288
0
                                        aTableOrElementInTable);
289
0
  // If the element is not in <table>, return error.
290
0
  if (NS_WARN_IF(!tableElement)) {
291
0
    aRv.Throw(NS_ERROR_FAILURE);
292
0
    return nullptr;
293
0
  }
294
0
295
0
  for (nsIContent* tableChild = tableElement->GetFirstChild();
296
0
       tableChild;
297
0
       tableChild = tableChild->GetNextSibling()) {
298
0
    if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
299
0
      // Found a row directly under <table>
300
0
      return tableChild->AsElement();
301
0
    }
302
0
    // <table> can have table section elements like <tbody>.  <tr> elements
303
0
    // may be children of them.
304
0
    if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody,
305
0
                                        nsGkAtoms::thead,
306
0
                                        nsGkAtoms::tfoot)) {
307
0
      for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
308
0
           tableSectionChild;
309
0
           tableSectionChild = tableSectionChild->GetNextSibling()) {
310
0
        if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
311
0
          return tableSectionChild->AsElement();
312
0
        }
313
0
      }
314
0
    }
315
0
  }
316
0
  // Don't return error when there is no <tr> element in the <table>.
317
0
  return nullptr;
318
0
}
319
320
Element*
321
HTMLEditor::GetNextTableRowElement(Element& aTableRowElement,
322
                                   ErrorResult& aRv) const
323
0
{
324
0
  MOZ_ASSERT(!aRv.Failed());
325
0
326
0
  if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
327
0
    aRv.Throw(NS_ERROR_FAILURE);
328
0
    return nullptr;
329
0
  }
330
0
331
0
  for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
332
0
       maybeNextRow;
333
0
       maybeNextRow = maybeNextRow->GetNextSibling()) {
334
0
    if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
335
0
      return maybeNextRow->AsElement();
336
0
    }
337
0
  }
338
0
339
0
  // In current table section (e.g., <tbody>), there is no <tr> element.
340
0
  // Then, check the following table sections.
341
0
  Element* parentElementOfRow = aTableRowElement.GetParentElement();
342
0
  if (NS_WARN_IF(!parentElementOfRow)) {
343
0
    aRv.Throw(NS_ERROR_FAILURE);
344
0
    return nullptr;
345
0
  }
346
0
347
0
  // Basically, <tr> elements should be in table section elements even if
348
0
  // they are not written in the source explicitly.  However, for preventing
349
0
  // cross table boundary, check it now.
350
0
  if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
351
0
    // Don't return error since this means just not found.
352
0
    return nullptr;
353
0
  }
354
0
355
0
  for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
356
0
       maybeNextTableSection;
357
0
       maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
358
0
    // If the sibling of parent of given <tr> is a table section element,
359
0
    // check its children.
360
0
    if (maybeNextTableSection->IsAnyOfHTMLElements(nsGkAtoms::tbody,
361
0
                                                   nsGkAtoms::thead,
362
0
                                                   nsGkAtoms::tfoot)) {
363
0
      for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
364
0
           maybeNextRow;
365
0
           maybeNextRow = maybeNextRow->GetNextSibling()) {
366
0
        if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
367
0
          return maybeNextRow->AsElement();
368
0
        }
369
0
      }
370
0
    }
371
0
    // I'm not sure whether this is a possible case since table section
372
0
    // elements are created automatically.  However, DOM API may create
373
0
    // <tr> elements without table section elements.  So, let's check it.
374
0
    else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
375
0
      return maybeNextTableSection->AsElement();
376
0
    }
377
0
  }
378
0
  // Don't return error when the given <tr> element is the last <tr> element in
379
0
  // the <table>.
380
0
  return nullptr;
381
0
}
382
383
nsresult
384
HTMLEditor::GetLastCellInRow(nsINode* aRowNode,
385
                             nsINode** aCellNode)
386
0
{
387
0
  NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER);
388
0
389
0
  *aCellNode = nullptr;
390
0
391
0
  NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
392
0
393
0
  nsCOMPtr<nsINode> rowChild = aRowNode->GetLastChild();
394
0
395
0
  while (rowChild && !HTMLEditUtils::IsTableCell(rowChild)) {
396
0
    // Skip over textnodes
397
0
    rowChild = rowChild->GetPreviousSibling();
398
0
  }
399
0
  if (rowChild) {
400
0
    rowChild.forget(aCellNode);
401
0
    return NS_OK;
402
0
  }
403
0
  // If here, cell was not found
404
0
  return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
405
0
}
406
407
NS_IMETHODIMP
408
HTMLEditor::InsertTableColumn(int32_t aNumberOfColumnsToInsert,
409
                              bool aInsertAfterSelectedCell)
410
0
{
411
0
  nsresult rv =
412
0
    InsertTableColumnsWithTransaction(aNumberOfColumnsToInsert,
413
0
      aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell :
414
0
                                 InsertPosition::eBeforeSelectedCell);
415
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
416
0
    return NS_ERROR_FAILURE;
417
0
  }
418
0
  return NS_OK;
419
0
}
420
421
nsresult
422
HTMLEditor::InsertTableColumnsWithTransaction(int32_t aNumberOfColumnsToInsert,
423
                                              InsertPosition aInsertPosition)
424
0
{
425
0
  RefPtr<Selection> selection;
426
0
  RefPtr<Element> table;
427
0
  RefPtr<Element> curCell;
428
0
  int32_t startRowIndex, startColIndex;
429
0
  nsresult rv = GetCellContext(getter_AddRefs(selection),
430
0
                               getter_AddRefs(table),
431
0
                               getter_AddRefs(curCell),
432
0
                               nullptr, nullptr,
433
0
                               &startRowIndex, &startColIndex);
434
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
435
0
    return rv;
436
0
  }
437
0
  if (NS_WARN_IF(!curCell)) {
438
0
    // Don't fail if no cell found.
439
0
    return NS_OK;
440
0
  }
441
0
442
0
  // Get more data for current cell, we need rowspan value.
443
0
  int32_t curStartRowIndex = 0, curStartColIndex = 0;
444
0
  int32_t rowSpan = 0, colSpan = 0;
445
0
  int32_t actualRowSpan = 0, actualColSpan = 0;
446
0
  bool isSelected = false;
447
0
  rv = GetCellDataAt(table, startRowIndex, startColIndex,
448
0
                     getter_AddRefs(curCell),
449
0
                     &curStartRowIndex, &curStartColIndex,
450
0
                     &rowSpan, &colSpan,
451
0
                     &actualRowSpan, &actualColSpan, &isSelected);
452
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
453
0
    return rv;
454
0
  }
455
0
  if (NS_WARN_IF(!curCell)) {
456
0
    return NS_ERROR_FAILURE;
457
0
  }
458
0
459
0
  ErrorResult error;
460
0
  TableSize tableSize(*this, *table, error);
461
0
  if (NS_WARN_IF(error.Failed())) {
462
0
    return error.StealNSResult();
463
0
  }
464
0
  // Should not be empty since we've already found a cell.
465
0
  MOZ_ASSERT(!tableSize.IsEmpty());
466
0
467
0
  AutoPlaceholderBatch beginBatching(this);
468
0
  // Prevent auto insertion of <br> element in new cell until we're done.
469
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
470
0
                                      *this, EditSubAction::eInsertNode,
471
0
                                      nsIEditor::eNext);
472
0
473
0
  switch (aInsertPosition) {
474
0
    case InsertPosition::eBeforeSelectedCell:
475
0
      break;
476
0
    case InsertPosition::eAfterSelectedCell:
477
0
      // Use column after current cell.
478
0
      startColIndex += actualColSpan;
479
0
480
0
      // Detect when user is adding after a colspan=0 case.
481
0
      // Assume they want to stop the "0" behavior and really add a new column.
482
0
      // Thus we set the colspan to its true value.
483
0
      if (!colSpan) {
484
0
        SetColSpan(curCell, actualColSpan);
485
0
      }
486
0
      break;
487
0
    default:
488
0
      MOZ_ASSERT_UNREACHABLE("Invalid InsertPosition");
489
0
  }
490
0
491
0
  // We control selection resetting after the insert.
492
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
493
0
                                             startColIndex, ePreviousRow,
494
0
                                             false);
495
0
  // Suppress Rules System selection munging.
496
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
497
0
498
0
  // If we are inserting after all existing columns, make sure table is
499
0
  // "well formed" before appending new column.
500
0
  // XXX As far as I've tested, NormalizeTable() always fails to normalize
501
0
  //     non-rectangular table.  So, the following GetCellDataAt() will
502
0
  //     fail if the table is not rectangle.
503
0
  if (startColIndex >= tableSize.mColumnCount) {
504
0
    if (NS_WARN_IF(!selection)) {
505
0
      return NS_ERROR_FAILURE;
506
0
    }
507
0
    DebugOnly<nsresult> rv = NormalizeTable(*selection, *table);
508
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to normalize the table");
509
0
  }
510
0
511
0
  RefPtr<Element> rowElement;
512
0
  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
513
0
    if (startColIndex < tableSize.mColumnCount) {
514
0
      // We are inserting before an existing column.
515
0
      int32_t curStartRowIndex = 0, curStartColIndex = 0;
516
0
      int32_t rowSpan = 0, colSpan = 0;
517
0
      int32_t actualRowSpan = 0, actualColSpan = 0;
518
0
      bool isSelected = false;
519
0
      rv = GetCellDataAt(table, rowIndex, startColIndex,
520
0
                         getter_AddRefs(curCell),
521
0
                         &curStartRowIndex, &curStartColIndex,
522
0
                         &rowSpan, &colSpan,
523
0
                         &actualRowSpan, &actualColSpan, &isSelected);
524
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
525
0
        return rv;
526
0
      }
527
0
528
0
      // Don't fail entire process if we fail to find a cell (may fail just in
529
0
      // particular rows with < adequate cells per row).
530
0
      if (!curCell) {
531
0
        continue;
532
0
      }
533
0
534
0
      if (curStartColIndex < startColIndex) {
535
0
        // If we have a cell spanning this location, simply increase its
536
0
        // colspan to keep table rectangular.
537
0
        // Note: we do nothing if colsspan=0, since it should automatically
538
0
        // span the new column.
539
0
        if (colSpan > 0) {
540
0
          SetColSpan(curCell, colSpan + aNumberOfColumnsToInsert);
541
0
        }
542
0
        continue;
543
0
      }
544
0
545
0
      // Simply set selection to the current cell. So, we can let
546
0
      // InsertTableCellsWithTransaction() do the work.  Insert a new cell
547
0
      // before current one.
548
0
      IgnoredErrorResult ignoredError;
549
0
      selection->Collapse(RawRangeBoundary(curCell, 0), ignoredError);
550
0
      NS_WARNING_ASSERTION(!ignoredError.Failed(),
551
0
        "Failed to collapse Selection into the cell");
552
0
      rv = InsertTableCellsWithTransaction(aNumberOfColumnsToInsert,
553
0
                                           InsertPosition::eBeforeSelectedCell);
554
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert a cell element");
555
0
      continue;
556
0
    }
557
0
558
0
    // Get current row and append new cells after last cell in row
559
0
    if (!rowIndex) {
560
0
      rowElement = GetFirstTableRowElement(*table, error);
561
0
      if (NS_WARN_IF(error.Failed())) {
562
0
        return error.StealNSResult();
563
0
      }
564
0
      if (NS_WARN_IF(!rowElement)) {
565
0
        continue;
566
0
      }
567
0
    } else {
568
0
      if (NS_WARN_IF(!rowElement)) {
569
0
        // XXX Looks like that when rowIndex is 0, startColIndex is always
570
0
        //     same as or larger than tableSize.mColumnCount.  Is it true?
571
0
        return NS_ERROR_FAILURE;
572
0
      }
573
0
      rowElement = GetNextTableRowElement(*rowElement, error);
574
0
      if (NS_WARN_IF(error.Failed())) {
575
0
        return error.StealNSResult();
576
0
      }
577
0
      if (NS_WARN_IF(!rowElement)) {
578
0
        continue;
579
0
      }
580
0
    }
581
0
582
0
    nsCOMPtr<nsINode> lastCell;
583
0
    rv = GetLastCellInRow(rowElement, getter_AddRefs(lastCell));
584
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
585
0
      return rv;
586
0
    }
587
0
    if (NS_WARN_IF(!lastCell)) {
588
0
      return NS_ERROR_FAILURE;
589
0
    }
590
0
591
0
    curCell = lastCell->AsElement();
592
0
    // Simply add same number of cells to each row.  Although tempted to check
593
0
    // cell indexes for curCell, the effects of colspan > 1 in some cells makes
594
0
    // this futile.  We must use NormalizeTable first to assure that there are
595
0
    // cells in each cellmap location.
596
0
    IgnoredErrorResult ignoredError;
597
0
    selection->Collapse(RawRangeBoundary(curCell, 0), ignoredError);
598
0
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
599
0
      "Failed to collapse Selection into the cell");
600
0
    rv = InsertTableCellsWithTransaction(aNumberOfColumnsToInsert,
601
0
                                         InsertPosition::eAfterSelectedCell);
602
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert a cell element");
603
0
  }
604
0
  // XXX This is perhaps the result of the last call of
605
0
  //     InsertTableCellsWithTransaction().
606
0
  return rv;
607
0
}
608
609
NS_IMETHODIMP
610
HTMLEditor::InsertTableRow(int32_t aNumberOfRowsToInsert,
611
                           bool aInsertAfterSelectedCell)
612
0
{
613
0
  nsresult rv =
614
0
    InsertTableRowsWithTransaction(aNumberOfRowsToInsert,
615
0
      aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell :
616
0
                                 InsertPosition::eBeforeSelectedCell);
617
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
618
0
    return rv;
619
0
  }
620
0
  return NS_OK;
621
0
}
622
623
nsresult
624
HTMLEditor::InsertTableRowsWithTransaction(int32_t aNumberOfRowsToInsert,
625
                                           InsertPosition aInsertPosition)
626
0
{
627
0
  RefPtr<Element> table;
628
0
  RefPtr<Element> curCell;
629
0
630
0
  int32_t startRowIndex, startColIndex;
631
0
  nsresult rv = GetCellContext(nullptr,
632
0
                               getter_AddRefs(table),
633
0
                               getter_AddRefs(curCell),
634
0
                               nullptr, nullptr,
635
0
                               &startRowIndex, &startColIndex);
636
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
637
0
    return rv;
638
0
  }
639
0
  if (NS_WARN_IF(!curCell)) {
640
0
    // Don't fail if no cell found.
641
0
    return NS_OK;
642
0
  }
643
0
644
0
  // Get more data for current cell in row we are inserting at because we need
645
0
  // colspan.
646
0
  int32_t curStartRowIndex = 0, curStartColIndex = 0;
647
0
  int32_t rowSpan = 0, colSpan = 0;
648
0
  int32_t actualRowSpan = 0, actualColSpan = 0;
649
0
  bool isSelected = false;
650
0
  rv = GetCellDataAt(table, startRowIndex, startColIndex,
651
0
                     getter_AddRefs(curCell),
652
0
                     &curStartRowIndex, &curStartColIndex,
653
0
                     &rowSpan, &colSpan,
654
0
                     &actualRowSpan, &actualColSpan, &isSelected);
655
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
656
0
    return rv;
657
0
  }
658
0
  if (NS_WARN_IF(!curCell)) {
659
0
    return NS_ERROR_FAILURE;
660
0
  }
661
0
662
0
  ErrorResult error;
663
0
  TableSize tableSize(*this, *table, error);
664
0
  if (NS_WARN_IF(error.Failed())) {
665
0
    return error.StealNSResult();
666
0
  }
667
0
  // Should not be empty since we've already found a cell.
668
0
  MOZ_ASSERT(!tableSize.IsEmpty());
669
0
670
0
  AutoPlaceholderBatch beginBatching(this);
671
0
  // Prevent auto insertion of BR in new cell until we're done
672
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
673
0
                                      *this, EditSubAction::eInsertNode,
674
0
                                      nsIEditor::eNext);
675
0
676
0
  switch (aInsertPosition) {
677
0
    case InsertPosition::eBeforeSelectedCell:
678
0
      break;
679
0
    case InsertPosition::eAfterSelectedCell:
680
0
      // Use row after current cell.
681
0
      startRowIndex += actualRowSpan;
682
0
683
0
      // Detect when user is adding after a rowspan=0 case.
684
0
      // Assume they want to stop the "0" behavior and really add a new row.
685
0
      // Thus we set the rowspan to its true value.
686
0
      if (!rowSpan) {
687
0
        SetRowSpan(curCell, actualRowSpan);
688
0
      }
689
0
      break;
690
0
    default:
691
0
      MOZ_ASSERT_UNREACHABLE("Invalid InsertPosition");
692
0
  }
693
0
694
0
  // We control selection resetting after the insert.
695
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
696
0
                                             startColIndex, ePreviousColumn,
697
0
                                             false);
698
0
  // Suppress Rules System selection munging.
699
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
700
0
701
0
  RefPtr<Element> cellForRowParent;
702
0
  int32_t cellsInRow = 0;
703
0
  if (startRowIndex < tableSize.mRowCount) {
704
0
    // We are inserting above an existing row.  Get each cell in the insert
705
0
    // row to adjust for colspan effects while we count how many cells are
706
0
    // needed.
707
0
    for (int32_t colIndex = 0, actualColSpan = 0;; colIndex += actualColSpan) {
708
0
      RefPtr<Element> cellElement;
709
0
      int32_t curStartRowIndex = 0, curStartColIndex = 0;
710
0
      int32_t rowSpan = 0, colSpan = 0;
711
0
      int32_t actualRowSpan = 0;
712
0
      nsresult rv = GetCellDataAt(table, startRowIndex, colIndex,
713
0
                                  getter_AddRefs(cellElement),
714
0
                                  &curStartRowIndex, &curStartColIndex,
715
0
                                  &rowSpan, &colSpan,
716
0
                                  &actualRowSpan, &actualColSpan,
717
0
                                  &isSelected);
718
0
      if (NS_FAILED(rv)) {
719
0
        break; // Perhaps, we reach end of the row.
720
0
      }
721
0
722
0
      if (NS_WARN_IF(!cellElement)) {
723
0
        // XXX What's this case?
724
0
        actualColSpan = 1;
725
0
        continue;
726
0
      }
727
0
728
0
      if (curStartRowIndex < startRowIndex) {
729
0
        // We have a cell spanning this location.  Increase its rowspan.
730
0
        // Note that if rowspan is 0, we do nothing since that cell should
731
0
        // automatically extend into the new row.
732
0
        if (rowSpan > 0) {
733
0
          SetRowSpan(cellElement, rowSpan + aNumberOfRowsToInsert);
734
0
        }
735
0
        continue;
736
0
      }
737
0
738
0
      cellsInRow += actualColSpan;
739
0
      if (!cellForRowParent) {
740
0
        cellForRowParent = std::move(cellElement);
741
0
      }
742
0
    }
743
0
  } else {
744
0
    // We are adding a new row after all others.  If it weren't for colspan=0
745
0
    // effect,  we could simply use tableSize.mColumnCount for number of new
746
0
    // cells...
747
0
    // XXX colspan=0 support has now been removed in table layout so maybe this
748
0
    //     can be cleaned up now? (bug 1243183)
749
0
    cellsInRow = tableSize.mColumnCount;
750
0
751
0
    // but we must compensate for all cells with rowspan = 0 in the last row.
752
0
    const int32_t kLastRowIndex = tableSize.mRowCount - 1;
753
0
    for (int32_t colIndex = 0, actualColSpan = 0;; colIndex += actualColSpan) {
754
0
      RefPtr<Element> cellElement;
755
0
      int32_t curStartRowIndex = 0, curStartColIndex = 0;
756
0
      int32_t rowSpan = 0, colSpan = 0;
757
0
      int32_t actualRowSpan = 0;
758
0
      nsresult rv = GetCellDataAt(table, kLastRowIndex, colIndex,
759
0
                                  getter_AddRefs(cellElement),
760
0
                                  &curStartRowIndex, &curStartColIndex,
761
0
                                  &rowSpan, &colSpan,
762
0
                                  &actualRowSpan, &actualColSpan,
763
0
                                  &isSelected);
764
0
      if (NS_FAILED(rv)) {
765
0
        break; // Perhaps, we reach end of the row.
766
0
      }
767
0
768
0
      if (!rowSpan) {
769
0
        MOZ_ASSERT(cellsInRow >= actualColSpan);
770
0
        cellsInRow -= actualColSpan;
771
0
      }
772
0
773
0
      // Save cell from the last row that we will use below
774
0
      if (!cellForRowParent && curStartRowIndex == kLastRowIndex) {
775
0
        cellForRowParent = std::move(cellElement);
776
0
      }
777
0
    }
778
0
  }
779
0
780
0
  if (NS_WARN_IF(!cellsInRow)) {
781
0
    // There is no cell element in the last row??
782
0
    return NS_OK;
783
0
  }
784
0
785
0
  if (NS_WARN_IF(!cellForRowParent)) {
786
0
    return NS_ERROR_FAILURE;
787
0
  }
788
0
  Element* parentRow =
789
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::tr, *cellForRowParent);
790
0
  if (NS_WARN_IF(!parentRow)) {
791
0
    return NS_ERROR_FAILURE;
792
0
  }
793
0
794
0
  // The row parent and offset where we will insert new row.
795
0
  EditorDOMPoint pointToInsert(parentRow);
796
0
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
797
0
    return NS_ERROR_FAILURE;
798
0
  }
799
0
  // Adjust for when adding past the end.
800
0
  if (aInsertPosition == InsertPosition::eAfterSelectedCell &&
801
0
      startRowIndex >= tableSize.mRowCount) {
802
0
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
803
0
    NS_WARNING_ASSERTION(advanced, "Failed to advance offset");
804
0
  }
805
0
806
0
  for (int32_t row = 0; row < aNumberOfRowsToInsert; row++) {
807
0
    // Create a new row
808
0
    RefPtr<Element> newRow = CreateElementWithDefaults(*nsGkAtoms::tr);
809
0
    if (NS_WARN_IF(!newRow)) {
810
0
      return NS_ERROR_FAILURE;
811
0
    }
812
0
813
0
    for (int32_t i = 0; i < cellsInRow; i++) {
814
0
      RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
815
0
      if (NS_WARN_IF(!newCell)) {
816
0
        return NS_ERROR_FAILURE;
817
0
      }
818
0
      newRow->AppendChild(*newCell, error);
819
0
      if (NS_WARN_IF(error.Failed())) {
820
0
        return error.StealNSResult();
821
0
      }
822
0
    }
823
0
824
0
    AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
825
0
    rv = InsertNodeWithTransaction(*newRow, pointToInsert);
826
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
827
0
      return rv;
828
0
    }
829
0
  }
830
0
831
0
  // SetSelectionAfterTableEdit from AutoSelectionSetterAfterTableEdit will
832
0
  // access frame selection, so we need reframe.
833
0
  // Because GetTableCellElementAt() depends on frame.
834
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
835
0
  if (presShell) {
836
0
    presShell->FlushPendingNotifications(FlushType::Frames);
837
0
  }
838
0
839
0
  return NS_OK;
840
0
}
841
842
nsresult
843
HTMLEditor::DeleteTableElementAndChildrenWithTransaction(Selection& aSelection,
844
                                                         Element& aTableElement)
845
0
{
846
0
  // Block selectionchange event.  It's enough to dispatch selectionchange
847
0
  // event immediately after removing the table element.
848
0
  {
849
0
    AutoHideSelectionChanges hideSelection(&aSelection);
850
0
851
0
    // Select the <table> element after clear current selection.
852
0
    if (aSelection.RangeCount()) {
853
0
      nsresult rv = aSelection.RemoveAllRangesTemporarily();
854
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
855
0
        return rv;
856
0
      }
857
0
    }
858
0
859
0
    RefPtr<nsRange> range = new nsRange(&aTableElement);
860
0
    ErrorResult error;
861
0
    range->SelectNode(aTableElement, error);
862
0
    if (NS_WARN_IF(error.Failed())) {
863
0
      return error.StealNSResult();
864
0
    }
865
0
    aSelection.AddRange(*range, error);
866
0
    if (NS_WARN_IF(error.Failed())) {
867
0
      return error.StealNSResult();
868
0
    }
869
0
870
#ifdef DEBUG
871
    range = aSelection.GetRangeAt(0);
872
    MOZ_ASSERT(range);
873
    MOZ_ASSERT(range->GetStartContainer() == aTableElement.GetParent());
874
    MOZ_ASSERT(range->GetEndContainer() == aTableElement.GetParent());
875
    MOZ_ASSERT(range->GetChildAtStartOffset() == &aTableElement);
876
    MOZ_ASSERT(range->GetChildAtEndOffset() == aTableElement.GetNextSibling());
877
#endif // #ifdef DEBUG
878
  }
879
0
880
0
  nsresult rv = DeleteSelectionAsSubAction(eNext, eStrip);
881
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
882
0
    return rv;
883
0
  }
884
0
  return NS_OK;
885
0
}
886
887
NS_IMETHODIMP
888
HTMLEditor::DeleteTable()
889
0
{
890
0
  RefPtr<Selection> selection;
891
0
  RefPtr<Element> table;
892
0
  nsresult rv = GetCellContext(getter_AddRefs(selection),
893
0
                               getter_AddRefs(table),
894
0
                               nullptr, nullptr, nullptr, nullptr, nullptr);
895
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
896
0
    return rv;
897
0
  }
898
0
  if (NS_WARN_IF(!selection) || NS_WARN_IF(!table)) {
899
0
    return NS_ERROR_FAILURE;
900
0
  }
901
0
902
0
  AutoPlaceholderBatch beginBatching(this);
903
0
  rv = DeleteTableElementAndChildrenWithTransaction(*selection, *table);
904
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
905
0
    return rv;
906
0
  }
907
0
  return NS_OK;
908
0
}
909
910
NS_IMETHODIMP
911
HTMLEditor::DeleteTableCell(int32_t aNumberOfCellsToDelete)
912
0
{
913
0
  nsresult rv = DeleteTableCellWithTransaction(aNumberOfCellsToDelete);
914
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
915
0
    return rv;
916
0
  }
917
0
  return NS_OK;
918
0
}
919
920
nsresult
921
HTMLEditor::DeleteTableCellWithTransaction(int32_t aNumberOfCellsToDelete)
922
0
{
923
0
  RefPtr<Selection> selection;
924
0
  RefPtr<Element> table;
925
0
  RefPtr<Element> cell;
926
0
  int32_t startRowIndex, startColIndex;
927
0
928
0
929
0
  nsresult rv = GetCellContext(getter_AddRefs(selection),
930
0
                               getter_AddRefs(table),
931
0
                               getter_AddRefs(cell),
932
0
                               nullptr, nullptr,
933
0
                               &startRowIndex, &startColIndex);
934
0
935
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
936
0
    return rv;
937
0
  }
938
0
  if (NS_WARN_IF(!table) || NS_WARN_IF(!cell)) {
939
0
    // Don't fail if we didn't find a table or cell.
940
0
    return NS_OK;
941
0
  }
942
0
943
0
  AutoPlaceholderBatch beginBatching(this);
944
0
  // Prevent rules testing until we're done
945
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
946
0
                                      *this, EditSubAction::eDeleteNode,
947
0
                                      nsIEditor::eNext);
948
0
949
0
  ErrorResult error;
950
0
  RefPtr<Element> firstSelectedCellElement =
951
0
    GetFirstSelectedTableCellElement(*selection, error);
952
0
  if (NS_WARN_IF(error.Failed())) {
953
0
    return error.StealNSResult();
954
0
  }
955
0
956
0
  MOZ_ASSERT(selection->RangeCount());
957
0
958
0
  TableSize tableSize(*this, *table, error);
959
0
  if (NS_WARN_IF(error.Failed())) {
960
0
    return error.StealNSResult();
961
0
  }
962
0
963
0
  MOZ_ASSERT(!tableSize.IsEmpty());
964
0
965
0
  // If only one cell is selected or no cell is selected, remove cells
966
0
  // starting from the first selected cell or a cell containing first
967
0
  // selection range.
968
0
  if (!firstSelectedCellElement || selection->RangeCount() == 1) {
969
0
    for (int32_t i = 0; i < aNumberOfCellsToDelete; i++) {
970
0
      rv = GetCellContext(getter_AddRefs(selection),
971
0
                          getter_AddRefs(table),
972
0
                          getter_AddRefs(cell),
973
0
                          nullptr, nullptr,
974
0
                          &startRowIndex, &startColIndex);
975
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
976
0
        return rv;
977
0
      }
978
0
      if (NS_WARN_IF(!table) || NS_WARN_IF(!cell)) {
979
0
        // Don't fail if no cell found
980
0
        return NS_OK;
981
0
      }
982
0
983
0
      int32_t numberOfCellsInRow = GetNumberOfCellsInRow(*table, startRowIndex);
984
0
      NS_WARNING_ASSERTION(numberOfCellsInRow >= 0,
985
0
        "Failed to count number of cells in the row");
986
0
987
0
      if (numberOfCellsInRow == 1) {
988
0
        // Remove <tr> or <table> if we're removing all cells in the row or
989
0
        // the table.
990
0
        if (tableSize.mRowCount == 1) {
991
0
          rv = DeleteTableElementAndChildrenWithTransaction(*selection, *table);
992
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
993
0
            return rv;
994
0
          }
995
0
          return NS_OK;
996
0
        }
997
0
998
0
        // We need to call DeleteSelectedTableRowsWithTransaction() to handle
999
0
        // cells with rowspan attribute.
1000
0
        rv = DeleteSelectedTableRowsWithTransaction(1);
1001
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1002
0
          return rv;
1003
0
        }
1004
0
1005
0
        // Adjust table rows simply.  In strictly speaking, we should
1006
0
        // recompute table size with the latest layout information since
1007
0
        // mutation event listener may have changed the DOM tree. However,
1008
0
        // this is not in usual path of Firefox.  So, we can assume that
1009
0
        // there are no mutation event listeners.
1010
0
        MOZ_ASSERT(tableSize.mRowCount);
1011
0
        tableSize.mRowCount--;
1012
0
        continue;
1013
0
      }
1014
0
1015
0
      // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1016
0
      // destructor
1017
0
      AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
1018
0
                                                 startColIndex,
1019
0
                                                 ePreviousColumn, false);
1020
0
      AutoTransactionsConserveSelection dontChangeSelection(*this);
1021
0
1022
0
      // XXX Removing cell element causes not adjusting colspan.
1023
0
      rv = DeleteNodeWithTransaction(*cell);
1024
0
      // If we fail, don't try to delete any more cells???
1025
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1026
0
        return rv;
1027
0
      }
1028
0
      // Note that we don't refer column number in this loop.  So, it must
1029
0
      // be safe not to recompute table size since number of row is synced
1030
0
      // above.
1031
0
    }
1032
0
    return NS_OK;
1033
0
  }
1034
0
1035
0
  // When 2 or more cells are selected, ignore aNumberOfCellsToRemove and
1036
0
  // remove all selected cells.
1037
0
  CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
1038
0
  if (NS_WARN_IF(error.Failed())) {
1039
0
    return error.StealNSResult();
1040
0
  }
1041
0
  cell = firstSelectedCellElement;
1042
0
  startRowIndex = firstCellIndexes.mRow;
1043
0
  startColIndex = firstCellIndexes.mColumn;
1044
0
1045
0
  // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1046
0
  // destructor
1047
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
1048
0
                                             startColIndex, ePreviousColumn,
1049
0
                                             false);
1050
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
1051
0
1052
0
  bool checkToDeleteRow = true;
1053
0
  bool checkToDeleteColumn = true;
1054
0
  while (cell) {
1055
0
    if (checkToDeleteRow) {
1056
0
      // Optimize to delete an entire row
1057
0
      // Clear so we don't repeat AllCellsInRowSelected within the same row
1058
0
      checkToDeleteRow = false;
1059
0
      if (AllCellsInRowSelected(table, startRowIndex, tableSize.mColumnCount)) {
1060
0
        // First, find the next cell in a different row to continue after we
1061
0
        // delete this row.
1062
0
        int32_t nextRow = startRowIndex;
1063
0
        while (nextRow == startRowIndex) {
1064
0
          cell = GetNextSelectedTableCellElement(*selection, error);
1065
0
          if (NS_WARN_IF(error.Failed())) {
1066
0
            return error.StealNSResult();
1067
0
          }
1068
0
          if (!cell) {
1069
0
            break;
1070
0
          }
1071
0
          CellIndexes nextSelectedCellIndexes(*cell, error);
1072
0
          if (NS_WARN_IF(error.Failed())) {
1073
0
            return error.StealNSResult();
1074
0
          }
1075
0
          nextRow = nextSelectedCellIndexes.mRow;
1076
0
          startColIndex = nextSelectedCellIndexes.mColumn;
1077
0
        }
1078
0
        if (tableSize.mRowCount == 1) {
1079
0
          rv = DeleteTableElementAndChildrenWithTransaction(*selection, *table);
1080
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
1081
0
            return rv;
1082
0
          }
1083
0
          return NS_OK;
1084
0
        }
1085
0
        rv = DeleteTableRowWithTransaction(*table, startRowIndex);
1086
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1087
0
          return rv;
1088
0
        }
1089
0
        // Adjust table rows simply.  In strictly speaking, we should
1090
0
        // recompute table size with the latest layout information since
1091
0
        // mutation event listener may have changed the DOM tree. However,
1092
0
        // this is not in usual path of Firefox.  So, we can assume that
1093
0
        // there are no mutation event listeners.
1094
0
        MOZ_ASSERT(tableSize.mRowCount);
1095
0
        tableSize.mRowCount--;
1096
0
        if (!cell) {
1097
0
          break;
1098
0
        }
1099
0
        // For the next cell: Subtract 1 for row we deleted
1100
0
        startRowIndex = nextRow - 1;
1101
0
        // Set true since we know we will look at a new row next
1102
0
        checkToDeleteRow = true;
1103
0
        continue;
1104
0
      }
1105
0
    }
1106
0
1107
0
    if (checkToDeleteColumn) {
1108
0
      // Optimize to delete an entire column
1109
0
      // Clear this so we don't repeat AllCellsInColSelected within the same Col
1110
0
      checkToDeleteColumn = false;
1111
0
      if (AllCellsInColumnSelected(table, startColIndex,
1112
0
                                   tableSize.mColumnCount)) {
1113
0
        // First, find the next cell in a different column to continue after
1114
0
        // we delete this column.
1115
0
        int32_t nextCol = startColIndex;
1116
0
        while (nextCol == startColIndex) {
1117
0
          cell = GetNextSelectedTableCellElement(*selection, error);
1118
0
          if (NS_WARN_IF(error.Failed())) {
1119
0
            return error.StealNSResult();
1120
0
          }
1121
0
          if (!cell) {
1122
0
            break;
1123
0
          }
1124
0
          CellIndexes nextSelectedCellIndexes(*cell, error);
1125
0
          if (NS_WARN_IF(error.Failed())) {
1126
0
            return error.StealNSResult();
1127
0
          }
1128
0
          startRowIndex = nextSelectedCellIndexes.mRow;
1129
0
          nextCol = nextSelectedCellIndexes.mColumn;
1130
0
        }
1131
0
        // Delete all cells which belong to the column.
1132
0
        rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1133
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1134
0
          return rv;
1135
0
        }
1136
0
        // Adjust table columns simply.  In strictly speaking, we should
1137
0
        // recompute table size with the latest layout information since
1138
0
        // mutation event listener may have changed the DOM tree. However,
1139
0
        // this is not in usual path of Firefox.  So, we can assume that
1140
0
        // there are no mutation event listeners.
1141
0
        MOZ_ASSERT(tableSize.mColumnCount);
1142
0
        tableSize.mColumnCount--;
1143
0
        if (!cell) {
1144
0
          break;
1145
0
        }
1146
0
        // For the next cell, subtract 1 for col. deleted
1147
0
        startColIndex = nextCol - 1;
1148
0
        // Set true since we know we will look at a new column next
1149
0
        checkToDeleteColumn = true;
1150
0
        continue;
1151
0
      }
1152
0
    }
1153
0
1154
0
    // First get the next cell to delete
1155
0
    RefPtr<Element> nextCell =
1156
0
      GetNextSelectedTableCellElement(*selection, error);
1157
0
    if (NS_WARN_IF(error.Failed())) {
1158
0
      return error.StealNSResult();
1159
0
    }
1160
0
1161
0
    // Then delete the cell
1162
0
    rv = DeleteNodeWithTransaction(*cell);
1163
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1164
0
      return rv;
1165
0
    }
1166
0
1167
0
    if (!nextCell) {
1168
0
      return NS_OK;
1169
0
    }
1170
0
1171
0
    CellIndexes nextCellIndexes(*nextCell, error);
1172
0
    if (NS_WARN_IF(error.Failed())) {
1173
0
      return error.StealNSResult();
1174
0
    }
1175
0
    startRowIndex = nextCellIndexes.mRow;
1176
0
    startColIndex = nextCellIndexes.mColumn;
1177
0
    cell = std::move(nextCell);
1178
0
    // When table cell is removed, table size of column may be changed.
1179
0
    // For example, if there are 2 rows, one has 2 cells, the other has
1180
0
    // 3 cells, tableSize.mColumnCount is 3.  When this removes a cell
1181
0
    // in the latter row, mColumnCount should be come 2.  However, we
1182
0
    // don't use mColumnCount in this loop, so, this must be okay for now.
1183
0
  }
1184
0
  return NS_OK;
1185
0
}
1186
1187
NS_IMETHODIMP
1188
HTMLEditor::DeleteTableCellContents()
1189
0
{
1190
0
  nsresult rv = DeleteTableCellContentsWithTransaction();
1191
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1192
0
    return rv;
1193
0
  }
1194
0
  return NS_OK;
1195
0
}
1196
1197
nsresult
1198
HTMLEditor::DeleteTableCellContentsWithTransaction()
1199
0
{
1200
0
  RefPtr<Selection> selection;
1201
0
  RefPtr<Element> table;
1202
0
  RefPtr<Element> cell;
1203
0
  int32_t startRowIndex, startColIndex;
1204
0
  nsresult rv = GetCellContext(getter_AddRefs(selection),
1205
0
                               getter_AddRefs(table),
1206
0
                               getter_AddRefs(cell),
1207
0
                               nullptr, nullptr,
1208
0
                               &startRowIndex, &startColIndex);
1209
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1210
0
    return rv;
1211
0
  }
1212
0
  if (NS_WARN_IF(!selection) || NS_WARN_IF(!cell)) {
1213
0
    // Don't fail if no cell found.
1214
0
    return NS_OK;
1215
0
  }
1216
0
1217
0
1218
0
  AutoPlaceholderBatch beginBatching(this);
1219
0
  // Prevent rules testing until we're done
1220
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1221
0
                                      *this, EditSubAction::eDeleteNode,
1222
0
                                      nsIEditor::eNext);
1223
0
  // Don't let Rules System change the selection
1224
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
1225
0
1226
0
  ErrorResult error;
1227
0
  RefPtr<Element> firstSelectedCellElement =
1228
0
    GetFirstSelectedTableCellElement(*selection, error);
1229
0
  if (NS_WARN_IF(error.Failed())) {
1230
0
    return error.StealNSResult();
1231
0
  }
1232
0
1233
0
  if (firstSelectedCellElement) {
1234
0
    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
1235
0
    if (NS_WARN_IF(error.Failed())) {
1236
0
      return error.StealNSResult();
1237
0
    }
1238
0
    cell = firstSelectedCellElement;
1239
0
    startRowIndex = firstCellIndexes.mRow;
1240
0
    startColIndex = firstCellIndexes.mColumn;
1241
0
  }
1242
0
1243
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
1244
0
                                             startColIndex, ePreviousColumn,
1245
0
                                             false);
1246
0
1247
0
  while (cell) {
1248
0
    DebugOnly<nsresult> rv = DeleteAllChildrenWithTransaction(*cell);
1249
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1250
0
      "Failed to remove all children of the cell element");
1251
0
    // If selection is not in cell-select mode, we should remove only the cell
1252
0
    // which contains first selection range.
1253
0
    if (!firstSelectedCellElement) {
1254
0
      return NS_OK;
1255
0
    }
1256
0
    // If there are 2 or more selected cells, keep handling the other selected
1257
0
    // cells.
1258
0
    cell = GetNextSelectedTableCellElement(*selection, error);
1259
0
    if (NS_WARN_IF(error.Failed())) {
1260
0
      return error.StealNSResult();
1261
0
    }
1262
0
  }
1263
0
  return NS_OK;
1264
0
}
1265
1266
NS_IMETHODIMP
1267
HTMLEditor::DeleteTableColumn(int32_t aNumberOfColumnsToDelete)
1268
0
{
1269
0
  nsresult rv =
1270
0
    DeleteSelectedTableColumnsWithTransaction(aNumberOfColumnsToDelete);
1271
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1272
0
    return rv;
1273
0
  }
1274
0
  return NS_OK;
1275
0
}
1276
1277
nsresult
1278
HTMLEditor::DeleteSelectedTableColumnsWithTransaction(
1279
              int32_t aNumberOfColumnsToDelete)
1280
0
{
1281
0
  RefPtr<Selection> selection;
1282
0
  RefPtr<Element> table;
1283
0
  RefPtr<Element> cell;
1284
0
  int32_t startRowIndex, startColIndex;
1285
0
  nsresult rv = GetCellContext(getter_AddRefs(selection),
1286
0
                               getter_AddRefs(table),
1287
0
                               getter_AddRefs(cell),
1288
0
                               nullptr, nullptr,
1289
0
                               &startRowIndex, &startColIndex);
1290
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1291
0
    return rv;
1292
0
  }
1293
0
  if (NS_WARN_IF(!selection) || NS_WARN_IF(!table) || NS_WARN_IF(!cell)) {
1294
0
    // Don't fail if no cell found.
1295
0
    return NS_OK;
1296
0
  }
1297
0
1298
0
  ErrorResult error;
1299
0
  TableSize tableSize(*this, *table, error);
1300
0
  if (NS_WARN_IF(error.Failed())) {
1301
0
    return error.StealNSResult();
1302
0
  }
1303
0
1304
0
  AutoPlaceholderBatch beginBatching(this);
1305
0
1306
0
  // Prevent rules testing until we're done
1307
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1308
0
                                      *this, EditSubAction::eDeleteNode,
1309
0
                                      nsIEditor::eNext);
1310
0
1311
0
  // Shortcut the case of deleting all columns in table
1312
0
  if (!startColIndex && aNumberOfColumnsToDelete >= tableSize.mColumnCount) {
1313
0
    rv = DeleteTableElementAndChildrenWithTransaction(*selection, *table);
1314
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1315
0
      return rv;
1316
0
    }
1317
0
    return NS_OK;
1318
0
  }
1319
0
1320
0
1321
0
  // Test if deletion is controlled by selected cells
1322
0
  RefPtr<Element> firstSelectedCellElement =
1323
0
    GetFirstSelectedTableCellElement(*selection, error);
1324
0
  if (NS_WARN_IF(error.Failed())) {
1325
0
    return error.StealNSResult();
1326
0
  }
1327
0
1328
0
  MOZ_ASSERT(selection->RangeCount());
1329
0
1330
0
  if (firstSelectedCellElement && selection->RangeCount() > 1) {
1331
0
    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
1332
0
    if (NS_WARN_IF(error.Failed())) {
1333
0
      return error.StealNSResult();
1334
0
    }
1335
0
    startRowIndex = firstCellIndexes.mRow;
1336
0
    startColIndex = firstCellIndexes.mColumn;
1337
0
  }
1338
0
1339
0
  // We control selection resetting after the insert...
1340
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
1341
0
                                             startColIndex, ePreviousRow,
1342
0
                                             false);
1343
0
1344
0
  // If 2 or more cells are not selected, removing columns starting from
1345
0
  // a column which contains first selection range.
1346
0
  if (!firstSelectedCellElement || selection->RangeCount() == 1) {
1347
0
    int32_t columnCountToRemove =
1348
0
      std::min(aNumberOfColumnsToDelete,
1349
0
               tableSize.mColumnCount - startColIndex);
1350
0
    for (int32_t i = 0; i < columnCountToRemove; i++) {
1351
0
      rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1352
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1353
0
        return rv;
1354
0
      }
1355
0
    }
1356
0
    return NS_OK;
1357
0
  }
1358
0
1359
0
  // If 2 or more cells are selected, remove all columns which contain selected
1360
0
  // cells.  I.e., we ignore aNumberOfColumnsToDelete in this case.
1361
0
  for (cell = firstSelectedCellElement; cell;) {
1362
0
    if (cell != firstSelectedCellElement) {
1363
0
      CellIndexes cellIndexes(*cell, error);
1364
0
      if (NS_WARN_IF(error.Failed())) {
1365
0
        return error.StealNSResult();
1366
0
      }
1367
0
      startRowIndex = cellIndexes.mRow;
1368
0
      startColIndex = cellIndexes.mColumn;
1369
0
    }
1370
0
    // Find the next cell in a different column
1371
0
    // to continue after we delete this column
1372
0
    int32_t nextCol = startColIndex;
1373
0
    while (nextCol == startColIndex) {
1374
0
      cell = GetNextSelectedTableCellElement(*selection, error);
1375
0
      if (NS_WARN_IF(error.Failed())) {
1376
0
        return error.StealNSResult();
1377
0
      }
1378
0
      if (!cell) {
1379
0
        break;
1380
0
      }
1381
0
      CellIndexes cellIndexes(*cell, error);
1382
0
      if (NS_WARN_IF(error.Failed())) {
1383
0
        return error.StealNSResult();
1384
0
      }
1385
0
      startRowIndex = cellIndexes.mRow;
1386
0
      nextCol = cellIndexes.mColumn;
1387
0
    }
1388
0
    rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1389
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1390
0
      return rv;
1391
0
    }
1392
0
  }
1393
0
  return NS_OK;
1394
0
}
1395
1396
nsresult
1397
HTMLEditor::DeleteTableColumnWithTransaction(Element& aTableElement,
1398
                                             int32_t aColumnIndex)
1399
0
{
1400
0
  // XXX Why don't this method remove proper <col> (and <colgroup>)?
1401
0
  ErrorResult error;
1402
0
  for (int32_t rowIndex = 0;; rowIndex++) {
1403
0
    RefPtr<Element> cell;
1404
0
    int32_t startRowIndex = 0, startColIndex = 0;
1405
0
    int32_t rowSpan = 0, colSpan = 0;
1406
0
    int32_t actualRowSpan = 0, actualColSpan = 0;
1407
0
    bool isSelected = false;
1408
0
    nsresult rv =
1409
0
      GetCellDataAt(&aTableElement, rowIndex, aColumnIndex,
1410
0
                    getter_AddRefs(cell),
1411
0
                    &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1412
0
                    &actualRowSpan, &actualColSpan, &isSelected);
1413
0
    // Failure means that there is no more row in the table.  In this case,
1414
0
    // we shouldn't return error since we just reach the end of the table.
1415
0
    // XXX Ideally, GetCellDataAt() should return
1416
0
    //     NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND in such case instead of
1417
0
    //     error.  However, it's used by a lot of methods, so, it's really
1418
0
    //     risky to change it.
1419
0
    if (NS_FAILED(rv) || !cell) {
1420
0
      return NS_OK;
1421
0
    }
1422
0
1423
0
    // Find cells that don't start in column we are deleting.
1424
0
    MOZ_ASSERT(colSpan >= 0);
1425
0
    if (startColIndex < aColumnIndex || colSpan != 1) {
1426
0
      // If we have a cell spanning this location, decrease its colspan to
1427
0
      // keep table rectangular, but if colspan is 0, it'll be adjusted
1428
0
      // automatically.
1429
0
      if (colSpan > 0) {
1430
0
        NS_WARNING_ASSERTION(colSpan > 1, "colspan should be 2 or larger");
1431
0
        SetColSpan(cell, colSpan - 1);
1432
0
      }
1433
0
      if (startColIndex == aColumnIndex) {
1434
0
        // Cell is in column to be deleted, but must have colspan > 1,
1435
0
        // so delete contents of cell instead of cell itself (We must have
1436
0
        // reset colspan above).
1437
0
        DebugOnly<nsresult> rv = DeleteAllChildrenWithTransaction(*cell);
1438
0
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1439
0
          "Failed to remove all children of the cell element");
1440
0
      }
1441
0
      // Skip rows which the removed cell spanned.
1442
0
      rowIndex += actualRowSpan - 1;
1443
0
      continue;
1444
0
    }
1445
0
1446
0
    // Delete the cell
1447
0
    int32_t numberOfCellsInRow =
1448
0
      GetNumberOfCellsInRow(aTableElement, rowIndex);
1449
0
    NS_WARNING_ASSERTION(numberOfCellsInRow > 0,
1450
0
      "Failed to count existing cells in the row");
1451
0
    if (numberOfCellsInRow != 1) {
1452
0
      // If removing cell is not the last cell of the row, we can just remove
1453
0
      // it.
1454
0
      rv = DeleteNodeWithTransaction(*cell);
1455
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1456
0
        return rv;
1457
0
      }
1458
0
      // Skip rows which the removed cell spanned.
1459
0
      rowIndex += actualRowSpan - 1;
1460
0
      continue;
1461
0
    }
1462
0
1463
0
    // When the cell is the last cell in the row, remove the row instead.
1464
0
    Element* parentRow =
1465
0
      GetElementOrParentByTagNameInternal(*nsGkAtoms::tr, *cell);
1466
0
    if (NS_WARN_IF(!parentRow)) {
1467
0
      return NS_ERROR_FAILURE;
1468
0
    }
1469
0
1470
0
    // Check if its the only row left in the table.  If so, we can delete
1471
0
    // the table instead.
1472
0
    TableSize tableSize(*this, aTableElement, error);
1473
0
    if (NS_WARN_IF(error.Failed())) {
1474
0
      return error.StealNSResult();
1475
0
    }
1476
0
1477
0
    if (tableSize.mRowCount == 1) {
1478
0
      // We're deleting the last row.  So, let's remove the <table> now.
1479
0
      RefPtr<Selection> selection = GetSelection();
1480
0
      if (NS_WARN_IF(!selection)) {
1481
0
        return NS_ERROR_FAILURE;
1482
0
      }
1483
0
      rv = DeleteTableElementAndChildrenWithTransaction(*selection,
1484
0
                                                        aTableElement);
1485
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1486
0
        return rv;
1487
0
      }
1488
0
      return NS_OK;
1489
0
    }
1490
0
1491
0
    // Delete the row by placing caret in cell we were to delete.  We need
1492
0
    // to call DeleteTableRowWithTransaction() to handle cells with rowspan.
1493
0
    rv = DeleteTableRowWithTransaction(aTableElement, startRowIndex);
1494
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1495
0
      return rv;
1496
0
    }
1497
0
1498
0
    // Note that we decrement rowIndex since a row was deleted.
1499
0
    rowIndex--;
1500
0
  }
1501
0
}
1502
1503
NS_IMETHODIMP
1504
HTMLEditor::DeleteTableRow(int32_t aNumberOfRowsToDelete)
1505
0
{
1506
0
  nsresult rv = DeleteSelectedTableRowsWithTransaction(aNumberOfRowsToDelete);
1507
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1508
0
    return rv;
1509
0
  }
1510
0
  return NS_OK;
1511
0
}
1512
1513
nsresult
1514
HTMLEditor::DeleteSelectedTableRowsWithTransaction(
1515
              int32_t aNumberOfRowsToDelete)
1516
0
{
1517
0
  RefPtr<Selection> selection;
1518
0
  RefPtr<Element> table;
1519
0
  RefPtr<Element> cell;
1520
0
  int32_t startRowIndex, startColIndex;
1521
0
  nsresult rv =  GetCellContext(getter_AddRefs(selection),
1522
0
                                getter_AddRefs(table),
1523
0
                                getter_AddRefs(cell),
1524
0
                                nullptr, nullptr,
1525
0
                                &startRowIndex, &startColIndex);
1526
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1527
0
    return rv;
1528
0
  }
1529
0
  if (NS_WARN_IF(!selection) || NS_WARN_IF(!table) || NS_WARN_IF(!cell)) {
1530
0
    // Don't fail if no cell found.
1531
0
    return NS_OK;
1532
0
  }
1533
0
1534
0
  ErrorResult error;
1535
0
  TableSize tableSize(*this, *table, error);
1536
0
  if (NS_WARN_IF(error.Failed())) {
1537
0
    return error.StealNSResult();
1538
0
  }
1539
0
1540
0
  AutoPlaceholderBatch beginBatching(this);
1541
0
1542
0
  // Prevent rules testing until we're done
1543
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1544
0
                                      *this, EditSubAction::eDeleteNode,
1545
0
                                      nsIEditor::eNext);
1546
0
1547
0
  // Shortcut the case of deleting all rows in table
1548
0
  if (!startRowIndex && aNumberOfRowsToDelete >= tableSize.mRowCount) {
1549
0
    rv = DeleteTableElementAndChildrenWithTransaction(*selection, *table);
1550
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1551
0
      return NS_ERROR_FAILURE;
1552
0
    }
1553
0
    return NS_OK;
1554
0
  }
1555
0
1556
0
  RefPtr<Element> firstSelectedCellElement =
1557
0
    GetFirstSelectedTableCellElement(*selection, error);
1558
0
  if (NS_WARN_IF(error.Failed())) {
1559
0
    return error.StealNSResult();
1560
0
  }
1561
0
1562
0
  MOZ_ASSERT(selection->RangeCount());
1563
0
1564
0
  if (firstSelectedCellElement && selection->RangeCount() > 1) {
1565
0
    // Fetch indexes again - may be different for selected cells
1566
0
    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
1567
0
    if (NS_WARN_IF(error.Failed())) {
1568
0
      return error.StealNSResult();
1569
0
    }
1570
0
    startRowIndex = firstCellIndexes.mRow;
1571
0
    startColIndex = firstCellIndexes.mColumn;
1572
0
  }
1573
0
1574
0
  //We control selection resetting after the insert...
1575
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
1576
0
                                             startColIndex, ePreviousRow,
1577
0
                                             false);
1578
0
  // Don't change selection during deletions
1579
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
1580
0
1581
0
  // XXX Perhaps, the following loops should collect <tr> elements to remove
1582
0
  //     first, then, remove them from the DOM tree since mutation event
1583
0
  //     listener may change the DOM tree during the loops.
1584
0
1585
0
  // If 2 or more cells are not selected, removing rows starting from
1586
0
  // a row which contains first selection range.
1587
0
  if (!firstSelectedCellElement || selection->RangeCount() == 1) {
1588
0
    int32_t rowCountToRemove =
1589
0
      std::min(aNumberOfRowsToDelete, tableSize.mRowCount - startRowIndex);
1590
0
    for (int32_t i = 0; i < rowCountToRemove; i++) {
1591
0
      nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
1592
0
      // If failed in current row, try the next
1593
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1594
0
        startRowIndex++;
1595
0
      }
1596
0
      // Check if there's a cell in the "next" row.
1597
0
      cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
1598
0
      if (!cell) {
1599
0
        return NS_OK;
1600
0
      }
1601
0
    }
1602
0
    return NS_OK;
1603
0
  }
1604
0
1605
0
  // If 2 or more cells are selected, remove all rows which contain selected
1606
0
  // cells.  I.e., we ignore aNumberOfRowsToDelete in this case.
1607
0
  for (cell = firstSelectedCellElement; cell;) {
1608
0
    if (cell != firstSelectedCellElement) {
1609
0
      CellIndexes cellIndexes(*cell, error);
1610
0
      if (NS_WARN_IF(error.Failed())) {
1611
0
        return error.StealNSResult();
1612
0
      }
1613
0
      startRowIndex = cellIndexes.mRow;
1614
0
      startColIndex = cellIndexes.mColumn;
1615
0
    }
1616
0
    // Find the next cell in a different row
1617
0
    // to continue after we delete this row
1618
0
    int32_t nextRow = startRowIndex;
1619
0
    while (nextRow == startRowIndex) {
1620
0
      cell = GetNextSelectedTableCellElement(*selection, error);
1621
0
      if (NS_WARN_IF(error.Failed())) {
1622
0
        return error.StealNSResult();
1623
0
      }
1624
0
      if (!cell) {
1625
0
        break;
1626
0
      }
1627
0
      CellIndexes cellIndexes(*cell, error);
1628
0
      if (NS_WARN_IF(error.Failed())) {
1629
0
        return error.StealNSResult();
1630
0
      }
1631
0
      nextRow = cellIndexes.mRow;
1632
0
      startColIndex = cellIndexes.mColumn;
1633
0
    }
1634
0
    // Delete the row containing selected cell(s).
1635
0
    rv = DeleteTableRowWithTransaction(*table, startRowIndex);
1636
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1637
0
      return rv;
1638
0
    }
1639
0
  }
1640
0
  return NS_OK;
1641
0
}
1642
1643
// Helper that doesn't batch or change the selection
1644
nsresult
1645
HTMLEditor::DeleteTableRowWithTransaction(Element& aTableElement,
1646
                                          int32_t aRowIndex)
1647
0
{
1648
0
  ErrorResult error;
1649
0
  TableSize tableSize(*this, aTableElement, error);
1650
0
  if (NS_WARN_IF(error.Failed())) {
1651
0
    return error.StealNSResult();
1652
0
  }
1653
0
1654
0
  // Prevent rules testing until we're done
1655
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(*
1656
0
                                      this, EditSubAction::eDeleteNode,
1657
0
                                      nsIEditor::eNext);
1658
0
1659
0
  // Scan through cells in row to do rowspan adjustments
1660
0
  // Note that after we delete row, startRowIndex will point to the cells in
1661
0
  // the next row to be deleted.
1662
0
1663
0
  // The list of cells we will change rowspan in and the new rowspan values
1664
0
  // for each.
1665
0
  struct MOZ_STACK_CLASS SpanCell final
1666
0
  {
1667
0
    RefPtr<Element> mElement;
1668
0
    int32_t mNewRowSpanValue;
1669
0
1670
0
    SpanCell(Element* aSpanCellElement,
1671
0
             int32_t aNewRowSpanValue)
1672
0
      : mElement(aSpanCellElement)
1673
0
      , mNewRowSpanValue(aNewRowSpanValue)
1674
0
    {
1675
0
    }
1676
0
  };
1677
0
  AutoTArray<SpanCell, 10> spanCellArray;
1678
0
  RefPtr<Element> cellInDeleteRow;
1679
0
  int32_t columnIndex = 0;
1680
0
  while (aRowIndex < tableSize.mRowCount &&
1681
0
         columnIndex < tableSize.mColumnCount) {
1682
0
    RefPtr<Element> cell;
1683
0
    int32_t startRowIndex = 0, startColIndex = 0;
1684
0
    int32_t rowSpan = 0, colSpan = 0;
1685
0
    int32_t actualRowSpan = 0, actualColSpan = 0;
1686
0
    bool isSelected = false;
1687
0
    nsresult rv =
1688
0
      GetCellDataAt(&aTableElement, aRowIndex, columnIndex,
1689
0
                    getter_AddRefs(cell),
1690
0
                    &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1691
0
                    &actualRowSpan, &actualColSpan, &isSelected);
1692
0
    // We don't fail if we don't find a cell, so this must be real bad
1693
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1694
0
      return rv;
1695
0
    }
1696
0
1697
0
    if (!cell) {
1698
0
      break;
1699
0
    }
1700
0
    // Compensate for cells that don't start or extend below the row we are
1701
0
    // deleting.
1702
0
    if (startRowIndex < aRowIndex) {
1703
0
      // If a cell starts in row above us, decrease its rowspan to keep table
1704
0
      // rectangular but we don't need to do this if rowspan=0, since it will
1705
0
      // be automatically adjusted.
1706
0
      if (rowSpan > 0) {
1707
0
        // Build list of cells to change rowspan.  We can't do it now since
1708
0
        // it upsets cell map, so we will do it after deleting the row.
1709
0
        int32_t newRowSpanValue =
1710
0
          std::max(aRowIndex - startRowIndex, actualRowSpan - 1);
1711
0
        spanCellArray.AppendElement(SpanCell(cell, newRowSpanValue));
1712
0
      }
1713
0
    } else {
1714
0
      if (rowSpan > 1) {
1715
0
        // Cell spans below row to delete, so we must insert new cells to
1716
0
        // keep rows below.  Note that we test "rowSpan" so we don't do this
1717
0
        // if rowSpan = 0 (automatic readjustment).
1718
0
        int32_t aboveRowToInsertNewCellInto = aRowIndex - startRowIndex + 1;
1719
0
        int32_t numOfRawSpanRemainingBelow = actualRowSpan - 1;
1720
0
        rv = SplitCellIntoRows(&aTableElement, startRowIndex, startColIndex,
1721
0
                               aboveRowToInsertNewCellInto,
1722
0
                               numOfRawSpanRemainingBelow, nullptr);
1723
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1724
0
          return rv;
1725
0
        }
1726
0
      }
1727
0
      if (!cellInDeleteRow) {
1728
0
        cellInDeleteRow = cell; // Reference cell to find row to delete
1729
0
      }
1730
0
    }
1731
0
    // Skip over other columns spanned by this cell
1732
0
    columnIndex += actualColSpan;
1733
0
  }
1734
0
1735
0
  // Things are messed up if we didn't find a cell in the row!
1736
0
  if (NS_WARN_IF(!cellInDeleteRow)) {
1737
0
    return NS_ERROR_FAILURE;
1738
0
  }
1739
0
1740
0
  // Delete the entire row.
1741
0
  RefPtr<Element> parentRow =
1742
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::tr, *cellInDeleteRow);
1743
0
  if (parentRow) {
1744
0
    nsresult rv = DeleteNodeWithTransaction(*parentRow);
1745
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1746
0
      return rv;
1747
0
    }
1748
0
  }
1749
0
1750
0
  // Now we can set new rowspans for cells stored above.
1751
0
  for (SpanCell& spanCell : spanCellArray) {
1752
0
    if (NS_WARN_IF(!spanCell.mElement)) {
1753
0
      continue;
1754
0
    }
1755
0
    nsresult rv = SetRowSpan(spanCell.mElement, spanCell.mNewRowSpanValue);
1756
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1757
0
      return rv;
1758
0
    }
1759
0
  }
1760
0
  return NS_OK;
1761
0
}
1762
1763
1764
NS_IMETHODIMP
1765
HTMLEditor::SelectTable()
1766
0
{
1767
0
  RefPtr<Selection> selection = GetSelection();
1768
0
  if (NS_WARN_IF(!selection)) {
1769
0
    return NS_OK; // Don't fail if we didn't find a table.
1770
0
  }
1771
0
  RefPtr<Element> table =
1772
0
    GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
1773
0
  if (NS_WARN_IF(!table)) {
1774
0
    return NS_OK; // Don't fail if we didn't find a table.
1775
0
  }
1776
0
1777
0
  nsresult rv = ClearSelection();
1778
0
  if (NS_FAILED(rv)) {
1779
0
    return rv;
1780
0
  }
1781
0
  return AppendNodeToSelectionAsRange(table);
1782
0
}
1783
1784
NS_IMETHODIMP
1785
HTMLEditor::SelectTableCell()
1786
0
{
1787
0
  RefPtr<Selection> selection = GetSelection();
1788
0
  if (NS_WARN_IF(!selection)) {
1789
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1790
0
  }
1791
0
  RefPtr<Element> cell =
1792
0
    GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::td);
1793
0
  if (NS_WARN_IF(!cell)) {
1794
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1795
0
  }
1796
0
1797
0
  nsresult rv = ClearSelection();
1798
0
  if (NS_FAILED(rv)) {
1799
0
    return rv;
1800
0
  }
1801
0
  return AppendNodeToSelectionAsRange(cell);
1802
0
}
1803
1804
NS_IMETHODIMP
1805
HTMLEditor::SelectBlockOfCells(Element* aStartCell,
1806
                               Element* aEndCell)
1807
0
{
1808
0
  if (NS_WARN_IF(!aStartCell) || NS_WARN_IF(!aEndCell)) {
1809
0
    return NS_ERROR_INVALID_ARG;
1810
0
  }
1811
0
1812
0
  RefPtr<Selection> selection = GetSelection();
1813
0
  if (NS_WARN_IF(!selection)) {
1814
0
    return NS_ERROR_FAILURE;
1815
0
  }
1816
0
1817
0
  RefPtr<Element> table =
1818
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aStartCell);
1819
0
  if (NS_WARN_IF(!table)) {
1820
0
    return NS_ERROR_FAILURE;
1821
0
  }
1822
0
1823
0
  RefPtr<Element> endTable =
1824
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aEndCell);
1825
0
  if (NS_WARN_IF(!endTable)) {
1826
0
    return NS_ERROR_FAILURE;
1827
0
  }
1828
0
1829
0
  // We can only select a block if within the same table,
1830
0
  //  so do nothing if not within one table
1831
0
  if (table != endTable) {
1832
0
    return NS_OK;
1833
0
  }
1834
0
1835
0
  ErrorResult error;
1836
0
  CellIndexes startCellIndexes(*aStartCell, error);
1837
0
  if (NS_WARN_IF(error.Failed())) {
1838
0
    return error.StealNSResult();
1839
0
  }
1840
0
  CellIndexes endCellIndexes(*aEndCell, error);
1841
0
  if (NS_WARN_IF(error.Failed())) {
1842
0
    return error.StealNSResult();
1843
0
  }
1844
0
1845
0
  // Suppress nsISelectionListener notification
1846
0
  //  until all selection changes are finished
1847
0
  SelectionBatcher selectionBatcher(selection);
1848
0
1849
0
  // Examine all cell nodes in current selection and
1850
0
  //  remove those outside the new block cell region
1851
0
  int32_t minColumn =
1852
0
    std::min(startCellIndexes.mColumn, endCellIndexes.mColumn);
1853
0
  int32_t minRow =
1854
0
    std::min(startCellIndexes.mRow, endCellIndexes.mRow);
1855
0
  int32_t maxColumn =
1856
0
    std::max(startCellIndexes.mColumn, endCellIndexes.mColumn);
1857
0
  int32_t maxRow =
1858
0
    std::max(startCellIndexes.mRow, endCellIndexes.mRow);
1859
0
1860
0
  RefPtr<Element> cell;
1861
0
  int32_t currentRowIndex, currentColIndex;
1862
0
  cell = GetFirstSelectedTableCellElement(*selection, error);
1863
0
  if (NS_WARN_IF(error.Failed())) {
1864
0
    return error.StealNSResult();
1865
0
  }
1866
0
  if (!cell) {
1867
0
    return NS_OK;
1868
0
  }
1869
0
  RefPtr<nsRange> range = selection->GetRangeAt(0);
1870
0
  MOZ_ASSERT(range);
1871
0
  while (cell) {
1872
0
    CellIndexes currentCellIndexes(*cell, error);
1873
0
    if (NS_WARN_IF(error.Failed())) {
1874
0
      return error.StealNSResult();
1875
0
    }
1876
0
    if (currentCellIndexes.mRow < maxRow ||
1877
0
        currentCellIndexes.mRow > maxRow ||
1878
0
        currentCellIndexes.mColumn < maxColumn ||
1879
0
        currentCellIndexes.mColumn > maxColumn) {
1880
0
      selection->RemoveRange(*range, IgnoreErrors());
1881
0
      // Since we've removed the range, decrement pointer to next range
1882
0
      MOZ_ASSERT(mSelectedCellIndex > 0);
1883
0
      mSelectedCellIndex--;
1884
0
    }
1885
0
    cell = GetNextSelectedTableCellElement(*selection, error);
1886
0
    if (NS_WARN_IF(error.Failed())) {
1887
0
      return error.StealNSResult();
1888
0
    }
1889
0
    if (cell) {
1890
0
      MOZ_ASSERT(mSelectedCellIndex > 0);
1891
0
      range = selection->GetRangeAt(mSelectedCellIndex - 1);
1892
0
    }
1893
0
  }
1894
0
1895
0
  int32_t rowSpan, colSpan, actualRowSpan, actualColSpan;
1896
0
  bool    isSelected;
1897
0
  nsresult rv = NS_OK;
1898
0
  for (int32_t row = minRow; row <= maxRow; row++) {
1899
0
    for (int32_t col = minColumn; col <= maxColumn;
1900
0
        col += std::max(actualColSpan, 1)) {
1901
0
      rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1902
0
                         &currentRowIndex, &currentColIndex,
1903
0
                         &rowSpan, &colSpan,
1904
0
                         &actualRowSpan, &actualColSpan, &isSelected);
1905
0
      if (NS_FAILED(rv)) {
1906
0
        break;
1907
0
      }
1908
0
      // Skip cells that already selected or are spanned from previous locations
1909
0
      if (!isSelected && cell &&
1910
0
          row == currentRowIndex && col == currentColIndex) {
1911
0
        rv = AppendNodeToSelectionAsRange(cell);
1912
0
        if (NS_FAILED(rv)) {
1913
0
          break;
1914
0
        }
1915
0
      }
1916
0
    }
1917
0
  }
1918
0
  // NS_OK, otherwise, the last failure of GetCellDataAt() or
1919
0
  // AppendNodeToSelectionAsRange().
1920
0
  return rv;
1921
0
}
1922
1923
NS_IMETHODIMP
1924
HTMLEditor::SelectAllTableCells()
1925
0
{
1926
0
  RefPtr<Selection> selection = GetSelection();
1927
0
  if (NS_WARN_IF(!selection)) {
1928
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1929
0
  }
1930
0
  RefPtr<Element> cell =
1931
0
    GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::td);
1932
0
  if (NS_WARN_IF(!cell)) {
1933
0
    // Don't fail if we didn't find a cell.
1934
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1935
0
  }
1936
0
1937
0
  RefPtr<Element> startCell = cell;
1938
0
1939
0
  // Get parent table
1940
0
  RefPtr<Element> table =
1941
0
    GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *cell);
1942
0
  if (NS_WARN_IF(!table)) {
1943
0
    return NS_ERROR_FAILURE;
1944
0
  }
1945
0
1946
0
  ErrorResult error;
1947
0
  TableSize tableSize(*this, *table, error);
1948
0
  if (NS_WARN_IF(error.Failed())) {
1949
0
    return error.StealNSResult();
1950
0
  }
1951
0
1952
0
  // Suppress nsISelectionListener notification
1953
0
  //  until all selection changes are finished
1954
0
  SelectionBatcher selectionBatcher(selection);
1955
0
1956
0
  // It is now safe to clear the selection
1957
0
  // BE SURE TO RESET IT BEFORE LEAVING!
1958
0
  nsresult rv = ClearSelection();
1959
0
1960
0
  // Select all cells in the same column as current cell
1961
0
  bool cellSelected = false;
1962
0
  int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
1963
0
  bool    isSelected;
1964
0
  for (int32_t row = 0; row < tableSize.mRowCount; row++) {
1965
0
    for (int32_t col = 0;
1966
0
         col < tableSize.mColumnCount;
1967
0
         col += std::max(actualColSpan, 1)) {
1968
0
      rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1969
0
                         &currentRowIndex, &currentColIndex,
1970
0
                         &rowSpan, &colSpan,
1971
0
                         &actualRowSpan, &actualColSpan, &isSelected);
1972
0
      if (NS_FAILED(rv)) {
1973
0
        break;
1974
0
      }
1975
0
      // Skip cells that are spanned from previous rows or columns
1976
0
      if (cell && row == currentRowIndex && col == currentColIndex) {
1977
0
        rv =  AppendNodeToSelectionAsRange(cell);
1978
0
        if (NS_FAILED(rv)) {
1979
0
          break;
1980
0
        }
1981
0
        cellSelected = true;
1982
0
      }
1983
0
    }
1984
0
  }
1985
0
  // Safety code to select starting cell if nothing else was selected
1986
0
  if (!cellSelected) {
1987
0
    return AppendNodeToSelectionAsRange(startCell);
1988
0
  }
1989
0
  // NS_OK, otherwise, the error of ClearSelection() when there is no column or
1990
0
  // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
1991
0
  return rv;
1992
0
}
1993
1994
NS_IMETHODIMP
1995
HTMLEditor::SelectTableRow()
1996
0
{
1997
0
  RefPtr<Selection> selection = GetSelection();
1998
0
  if (NS_WARN_IF(!selection)) {
1999
0
    // Don't fail if we didn't find a cell.
2000
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2001
0
  }
2002
0
  RefPtr<Element> cell =
2003
0
    GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::td);
2004
0
  if (NS_WARN_IF(!cell)) {
2005
0
    // Don't fail if we didn't find a cell.
2006
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2007
0
  }
2008
0
2009
0
  RefPtr<Element> startCell = cell;
2010
0
2011
0
  // Get table and location of cell:
2012
0
  RefPtr<Element> table;
2013
0
  int32_t startRowIndex, startColIndex;
2014
0
2015
0
  nsresult rv = GetCellContext(nullptr,
2016
0
                               getter_AddRefs(table),
2017
0
                               getter_AddRefs(cell),
2018
0
                               nullptr, nullptr,
2019
0
                               &startRowIndex, &startColIndex);
2020
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2021
0
    return rv;
2022
0
  }
2023
0
  if (NS_WARN_IF(!table)) {
2024
0
    return NS_ERROR_FAILURE;
2025
0
  }
2026
0
2027
0
  ErrorResult error;
2028
0
  TableSize tableSize(*this, *table, error);
2029
0
  if (NS_WARN_IF(error.Failed())) {
2030
0
    return error.StealNSResult();
2031
0
  }
2032
0
2033
0
  //Note: At this point, we could get first and last cells in row,
2034
0
  //  then call SelectBlockOfCells, but that would take just
2035
0
  //  a little less code, so the following is more efficient
2036
0
2037
0
  // Suppress nsISelectionListener notification
2038
0
  //  until all selection changes are finished
2039
0
  SelectionBatcher selectionBatcher(selection);
2040
0
2041
0
  // It is now safe to clear the selection
2042
0
  // BE SURE TO RESET IT BEFORE LEAVING!
2043
0
  rv = ClearSelection();
2044
0
2045
0
  // Select all cells in the same row as current cell
2046
0
  bool cellSelected = false;
2047
0
  int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
2048
0
  bool    isSelected;
2049
0
  for (int32_t col = 0;
2050
0
       col < tableSize.mColumnCount;
2051
0
       col += std::max(actualColSpan, 1)) {
2052
0
    rv = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell),
2053
0
                       &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
2054
0
                       &actualRowSpan, &actualColSpan, &isSelected);
2055
0
    if (NS_FAILED(rv)) {
2056
0
      break;
2057
0
    }
2058
0
    // Skip cells that are spanned from previous rows or columns
2059
0
    if (cell && currentRowIndex == startRowIndex && currentColIndex == col) {
2060
0
      rv = AppendNodeToSelectionAsRange(cell);
2061
0
      if (NS_FAILED(rv)) {
2062
0
        break;
2063
0
      }
2064
0
      cellSelected = true;
2065
0
    }
2066
0
  }
2067
0
  // Safety code to select starting cell if nothing else was selected
2068
0
  if (!cellSelected) {
2069
0
    return AppendNodeToSelectionAsRange(startCell);
2070
0
  }
2071
0
  // NS_OK, otherwise, the error of ClearSelection() when there is no column or
2072
0
  // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
2073
0
  return rv;
2074
0
}
2075
2076
NS_IMETHODIMP
2077
HTMLEditor::SelectTableColumn()
2078
0
{
2079
0
  RefPtr<Selection> selection = GetSelection();
2080
0
  if (NS_WARN_IF(!selection)) {
2081
0
    // Don't fail if we didn't find a cell.
2082
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2083
0
  }
2084
0
  RefPtr<Element> cell =
2085
0
    GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::td);
2086
0
  if (NS_WARN_IF(!cell)) {
2087
0
    // Don't fail if we didn't find a cell.
2088
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2089
0
  }
2090
0
2091
0
  RefPtr<Element> startCell = cell;
2092
0
2093
0
  // Get location of cell:
2094
0
  RefPtr<Element> table;
2095
0
  int32_t startRowIndex, startColIndex;
2096
0
2097
0
  nsresult rv = GetCellContext(nullptr,
2098
0
                               getter_AddRefs(table),
2099
0
                               getter_AddRefs(cell),
2100
0
                               nullptr, nullptr,
2101
0
                               &startRowIndex, &startColIndex);
2102
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2103
0
    return rv;
2104
0
  }
2105
0
  if (NS_WARN_IF(!table)) {
2106
0
    return NS_ERROR_FAILURE;
2107
0
  }
2108
0
2109
0
  ErrorResult error;
2110
0
  TableSize tableSize(*this, *table, error);
2111
0
  if (NS_WARN_IF(error.Failed())) {
2112
0
    return error.StealNSResult();
2113
0
  }
2114
0
2115
0
  // Suppress nsISelectionListener notification
2116
0
  //  until all selection changes are finished
2117
0
  SelectionBatcher selectionBatcher(selection);
2118
0
2119
0
  // It is now safe to clear the selection
2120
0
  // BE SURE TO RESET IT BEFORE LEAVING!
2121
0
  rv = ClearSelection();
2122
0
2123
0
  // Select all cells in the same column as current cell
2124
0
  bool cellSelected = false;
2125
0
  int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
2126
0
  bool    isSelected;
2127
0
  for (int32_t row = 0;
2128
0
       row < tableSize.mRowCount;
2129
0
       row += std::max(actualRowSpan, 1)) {
2130
0
    rv = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell),
2131
0
                       &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
2132
0
                       &actualRowSpan, &actualColSpan, &isSelected);
2133
0
    if (NS_FAILED(rv)) {
2134
0
      break;
2135
0
    }
2136
0
    // Skip cells that are spanned from previous rows or columns
2137
0
    if (cell && currentRowIndex == row && currentColIndex == startColIndex) {
2138
0
      rv = AppendNodeToSelectionAsRange(cell);
2139
0
      if (NS_FAILED(rv)) {
2140
0
        break;
2141
0
      }
2142
0
      cellSelected = true;
2143
0
    }
2144
0
  }
2145
0
  // Safety code to select starting cell if nothing else was selected
2146
0
  if (!cellSelected) {
2147
0
    return AppendNodeToSelectionAsRange(startCell);
2148
0
  }
2149
0
  // NS_OK, otherwise, the error of ClearSelection() when there is no row or
2150
0
  // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
2151
0
  return rv;
2152
0
}
2153
2154
NS_IMETHODIMP
2155
HTMLEditor::SplitTableCell()
2156
0
{
2157
0
  RefPtr<Element> table;
2158
0
  RefPtr<Element> cell;
2159
0
  int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan;
2160
0
  // Get cell, table, etc. at selection anchor node
2161
0
  nsresult rv = GetCellContext(nullptr,
2162
0
                               getter_AddRefs(table),
2163
0
                               getter_AddRefs(cell),
2164
0
                               nullptr, nullptr,
2165
0
                               &startRowIndex, &startColIndex);
2166
0
  NS_ENSURE_SUCCESS(rv, rv);
2167
0
  if (!table || !cell) {
2168
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2169
0
  }
2170
0
2171
0
  // We need rowspan and colspan data
2172
0
  rv = GetCellSpansAt(table, startRowIndex, startColIndex,
2173
0
                      actualRowSpan, actualColSpan);
2174
0
  NS_ENSURE_SUCCESS(rv, rv);
2175
0
2176
0
  // Must have some span to split
2177
0
  if (actualRowSpan <= 1 && actualColSpan <= 1) {
2178
0
    return NS_OK;
2179
0
  }
2180
0
2181
0
  AutoPlaceholderBatch beginBatching(this);
2182
0
  // Prevent auto insertion of BR in new cell until we're done
2183
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2184
0
                                      *this, EditSubAction::eInsertNode,
2185
0
                                      nsIEditor::eNext);
2186
0
2187
0
  // We reset selection
2188
0
  AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
2189
0
                                             startColIndex, ePreviousColumn,
2190
0
                                             false);
2191
0
  //...so suppress Rules System selection munging
2192
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
2193
0
2194
0
  RefPtr<Element> newCell;
2195
0
  int32_t rowIndex = startRowIndex;
2196
0
  int32_t rowSpanBelow, colSpanAfter;
2197
0
2198
0
  // Split up cell row-wise first into rowspan=1 above, and the rest below,
2199
0
  //  whittling away at the cell below until no more extra span
2200
0
  for (rowSpanBelow = actualRowSpan-1; rowSpanBelow >= 0; rowSpanBelow--) {
2201
0
    // We really split row-wise only if we had rowspan > 1
2202
0
    if (rowSpanBelow > 0) {
2203
0
      rv = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow,
2204
0
                             getter_AddRefs(newCell));
2205
0
      NS_ENSURE_SUCCESS(rv, rv);
2206
0
      CopyCellBackgroundColor(newCell, cell);
2207
0
    }
2208
0
    int32_t colIndex = startColIndex;
2209
0
    // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
2210
0
    for (colSpanAfter = actualColSpan-1; colSpanAfter > 0; colSpanAfter--) {
2211
0
      rv = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter,
2212
0
                                getter_AddRefs(newCell));
2213
0
      NS_ENSURE_SUCCESS(rv, rv);
2214
0
      CopyCellBackgroundColor(newCell, cell);
2215
0
      colIndex++;
2216
0
    }
2217
0
    // Point to the new cell and repeat
2218
0
    rowIndex++;
2219
0
  }
2220
0
  return NS_OK;
2221
0
}
2222
2223
nsresult
2224
HTMLEditor::CopyCellBackgroundColor(Element* aDestCell,
2225
                                    Element* aSourceCell)
2226
0
{
2227
0
  if (NS_WARN_IF(!aDestCell) || NS_WARN_IF(!aSourceCell)) {
2228
0
    return NS_ERROR_INVALID_ARG;
2229
0
  }
2230
0
2231
0
  // Copy backgournd color to new cell.
2232
0
  nsAutoString color;
2233
0
  bool isSet;
2234
0
  nsresult rv =
2235
0
    GetAttributeValue(aSourceCell, NS_LITERAL_STRING("bgcolor"),
2236
0
                      color, &isSet);
2237
0
  if (NS_FAILED(rv)) {
2238
0
    return rv;
2239
0
  }
2240
0
  if (!isSet) {
2241
0
    return NS_OK;
2242
0
  }
2243
0
  return SetAttributeWithTransaction(*aDestCell, *nsGkAtoms::bgcolor, color);
2244
0
}
2245
2246
nsresult
2247
HTMLEditor::SplitCellIntoColumns(Element* aTable,
2248
                                 int32_t aRowIndex,
2249
                                 int32_t aColIndex,
2250
                                 int32_t aColSpanLeft,
2251
                                 int32_t aColSpanRight,
2252
                                 Element** aNewCell)
2253
0
{
2254
0
  NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
2255
0
  if (aNewCell) {
2256
0
    *aNewCell = nullptr;
2257
0
  }
2258
0
2259
0
  RefPtr<Element> cell;
2260
0
  int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2261
0
  bool    isSelected;
2262
0
  nsresult rv =
2263
0
    GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
2264
0
                  &startRowIndex, &startColIndex,
2265
0
                  &rowSpan, &colSpan,
2266
0
                  &actualRowSpan, &actualColSpan, &isSelected);
2267
0
  NS_ENSURE_SUCCESS(rv, rv);
2268
0
  NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
2269
0
2270
0
  // We can't split!
2271
0
  if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) {
2272
0
    return NS_OK;
2273
0
  }
2274
0
2275
0
  // Reduce colspan of cell to split
2276
0
  rv = SetColSpan(cell, aColSpanLeft);
2277
0
  NS_ENSURE_SUCCESS(rv, rv);
2278
0
2279
0
  // Insert new cell after using the remaining span
2280
0
  //  and always get the new cell so we can copy the background color;
2281
0
  RefPtr<Element> newCell;
2282
0
  rv = InsertCell(cell, actualRowSpan, aColSpanRight, true, false,
2283
0
                  getter_AddRefs(newCell));
2284
0
  NS_ENSURE_SUCCESS(rv, rv);
2285
0
  if (!newCell) {
2286
0
    return NS_OK;
2287
0
  }
2288
0
  if (aNewCell) {
2289
0
    NS_ADDREF(*aNewCell = newCell.get());
2290
0
  }
2291
0
  return CopyCellBackgroundColor(newCell, cell);
2292
0
}
2293
2294
nsresult
2295
HTMLEditor::SplitCellIntoRows(Element* aTable,
2296
                              int32_t aRowIndex,
2297
                              int32_t aColIndex,
2298
                              int32_t aRowSpanAbove,
2299
                              int32_t aRowSpanBelow,
2300
                              Element** aNewCell)
2301
0
{
2302
0
  if (NS_WARN_IF(!aTable)) {
2303
0
    return NS_ERROR_INVALID_ARG;
2304
0
  }
2305
0
2306
0
  if (aNewCell) {
2307
0
    *aNewCell = nullptr;
2308
0
  }
2309
0
2310
0
  RefPtr<Element> cell;
2311
0
  int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2312
0
  bool    isSelected;
2313
0
  nsresult rv =
2314
0
    GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
2315
0
                  &startRowIndex, &startColIndex,
2316
0
                  &rowSpan, &colSpan,
2317
0
                  &actualRowSpan, &actualColSpan, &isSelected);
2318
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2319
0
    return rv;
2320
0
  }
2321
0
  if (NS_WARN_IF(!cell)) {
2322
0
    return NS_ERROR_FAILURE;
2323
0
  }
2324
0
2325
0
  // We can't split!
2326
0
  if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) {
2327
0
    return NS_OK;
2328
0
  }
2329
0
2330
0
  ErrorResult error;
2331
0
  TableSize tableSize(*this, *aTable, error);
2332
0
  if (NS_WARN_IF(error.Failed())) {
2333
0
    return error.StealNSResult();
2334
0
  }
2335
0
2336
0
  RefPtr<Element> cell2;
2337
0
  RefPtr<Element> lastCellFound;
2338
0
  int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
2339
0
  bool    isSelected2;
2340
0
  int32_t colIndex = 0;
2341
0
  bool insertAfter = (startColIndex > 0);
2342
0
  // This is the row we will insert new cell into
2343
0
  int32_t rowBelowIndex = startRowIndex+aRowSpanAbove;
2344
0
2345
0
  // Find a cell to insert before or after
2346
0
  for (;;) {
2347
0
    // Search for a cell to insert before
2348
0
    rv = GetCellDataAt(aTable, rowBelowIndex,
2349
0
                       colIndex, getter_AddRefs(cell2),
2350
0
                       &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2,
2351
0
                       &actualRowSpan2, &actualColSpan2, &isSelected2);
2352
0
    // If we fail here, it could be because row has bad rowspan values,
2353
0
    //   such as all cells having rowspan > 1 (Call FixRowSpan first!)
2354
0
    if (NS_FAILED(rv) || !cell) {
2355
0
      return NS_ERROR_FAILURE;
2356
0
    }
2357
0
2358
0
    // Skip over cells spanned from above (like the one we are splitting!)
2359
0
    if (cell2 && startRowIndex2 == rowBelowIndex) {
2360
0
      if (!insertAfter) {
2361
0
        // Inserting before, so stop at first cell in row we want to insert
2362
0
        // into.
2363
0
        break;
2364
0
      }
2365
0
      // New cell isn't first in row,
2366
0
      // so stop after we find the cell just before new cell's column
2367
0
      if (startColIndex2 + actualColSpan2 == startColIndex) {
2368
0
        break;
2369
0
      }
2370
0
      // If cell found is AFTER desired new cell colum,
2371
0
      //  we have multiple cells with rowspan > 1 that
2372
0
      //  prevented us from finding a cell to insert after...
2373
0
      if (startColIndex2 > startColIndex) {
2374
0
        // ... so instead insert before the cell we found
2375
0
        insertAfter = false;
2376
0
        break;
2377
0
      }
2378
0
      lastCellFound = cell2;
2379
0
    }
2380
0
    // Skip to next available cellmap location
2381
0
    colIndex += std::max(actualColSpan2, 1);
2382
0
2383
0
    // Done when past end of total number of columns
2384
0
    if (colIndex > tableSize.mColumnCount) {
2385
0
      break;
2386
0
    }
2387
0
  }
2388
0
2389
0
  if (!cell2 && lastCellFound) {
2390
0
    // Edge case where we didn't find a cell to insert after
2391
0
    //  or before because column(s) before desired column
2392
0
    //  and all columns after it are spanned from above.
2393
0
    //  We can insert after the last cell we found
2394
0
    cell2 = lastCellFound;
2395
0
    insertAfter = true; // Should always be true, but let's be sure
2396
0
  }
2397
0
2398
0
  // Reduce rowspan of cell to split
2399
0
  rv = SetRowSpan(cell, aRowSpanAbove);
2400
0
  NS_ENSURE_SUCCESS(rv, rv);
2401
0
2402
0
2403
0
  // Insert new cell after using the remaining span
2404
0
  //  and always get the new cell so we can copy the background color;
2405
0
  RefPtr<Element> newCell;
2406
0
  rv = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, false,
2407
0
                  getter_AddRefs(newCell));
2408
0
  NS_ENSURE_SUCCESS(rv, rv);
2409
0
  if (!newCell) {
2410
0
    return NS_OK;
2411
0
  }
2412
0
  if (aNewCell) {
2413
0
    NS_ADDREF(*aNewCell = newCell.get());
2414
0
  }
2415
0
  return CopyCellBackgroundColor(newCell, cell2);
2416
0
}
2417
2418
NS_IMETHODIMP
2419
HTMLEditor::SwitchTableCellHeaderType(Element* aSourceCell,
2420
                                      Element** aNewCell)
2421
0
{
2422
0
  if (NS_WARN_IF(!aSourceCell)) {
2423
0
    return NS_ERROR_INVALID_ARG;
2424
0
  }
2425
0
2426
0
  AutoPlaceholderBatch beginBatching(this);
2427
0
  // Prevent auto insertion of BR in new cell created by
2428
0
  // ReplaceContainerAndCloneAttributesWithTransaction().
2429
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2430
0
                                      *this, EditSubAction::eInsertNode,
2431
0
                                      nsIEditor::eNext);
2432
0
2433
0
  // Save current selection to restore when done.
2434
0
  // This is needed so ReplaceContainerAndCloneAttributesWithTransaction()
2435
0
  // can monitor selection when replacing nodes.
2436
0
  RefPtr<Selection> selection = GetSelection();
2437
0
  if (NS_WARN_IF(!selection)) {
2438
0
    return NS_ERROR_FAILURE;
2439
0
  }
2440
0
2441
0
  AutoSelectionRestorer selectionRestorer(selection, this);
2442
0
2443
0
  // Set to the opposite of current type
2444
0
  nsAtom* newCellName =
2445
0
    aSourceCell->IsHTMLElement(nsGkAtoms::td) ? nsGkAtoms::th : nsGkAtoms::td;
2446
0
2447
0
  // This creates new node, moves children, copies attributes (true)
2448
0
  //   and manages the selection!
2449
0
  RefPtr<Element> newCell =
2450
0
    ReplaceContainerAndCloneAttributesWithTransaction(*aSourceCell,
2451
0
                                                      *newCellName);
2452
0
  if (NS_WARN_IF(!newCell)) {
2453
0
    return NS_ERROR_FAILURE;
2454
0
  }
2455
0
2456
0
  // Return the new cell
2457
0
  if (aNewCell) {
2458
0
    newCell.forget(aNewCell);
2459
0
  }
2460
0
2461
0
  return NS_OK;
2462
0
}
2463
2464
NS_IMETHODIMP
2465
HTMLEditor::JoinTableCells(bool aMergeNonContiguousContents)
2466
0
{
2467
0
  RefPtr<Element> table;
2468
0
  RefPtr<Element> targetCell;
2469
0
  int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2470
0
  bool    isSelected;
2471
0
  RefPtr<Element> cell2;
2472
0
  int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
2473
0
  bool    isSelected2;
2474
0
2475
0
  // Get cell, table, etc. at selection anchor node
2476
0
  nsresult rv = GetCellContext(nullptr,
2477
0
                               getter_AddRefs(table),
2478
0
                               getter_AddRefs(targetCell),
2479
0
                               nullptr, nullptr,
2480
0
                               &startRowIndex, &startColIndex);
2481
0
  NS_ENSURE_SUCCESS(rv, rv);
2482
0
  if (!table || !targetCell) {
2483
0
    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2484
0
  }
2485
0
2486
0
  AutoPlaceholderBatch beginBatching(this);
2487
0
  //Don't let Rules System change the selection
2488
0
  AutoTransactionsConserveSelection dontChangeSelection(*this);
2489
0
2490
0
  // Note: We dont' use AutoSelectionSetterAfterTableEdit here so the selection
2491
0
  //  is retained after joining. This leaves the target cell selected
2492
0
  //  as well as the "non-contiguous" cells, so user can see what happened.
2493
0
2494
0
  RefPtr<Selection> selection = GetSelection();
2495
0
  if (NS_WARN_IF(!selection)) {
2496
0
    return NS_ERROR_FAILURE;
2497
0
  }
2498
0
2499
0
  ErrorResult error;
2500
0
  CellAndIndexes firstSelectedCell(*this, *selection, error);
2501
0
  if (NS_WARN_IF(error.Failed())) {
2502
0
    return error.StealNSResult();
2503
0
  }
2504
0
2505
0
  bool joinSelectedCells = false;
2506
0
  if (firstSelectedCell.mElement) {
2507
0
    RefPtr<Element> secondCell =
2508
0
      GetNextSelectedTableCellElement(*selection, error);
2509
0
    if (NS_WARN_IF(error.Failed())) {
2510
0
      return error.StealNSResult();
2511
0
    }
2512
0
2513
0
    // If only one cell is selected, join with cell to the right
2514
0
    joinSelectedCells = (secondCell != nullptr);
2515
0
  }
2516
0
2517
0
  if (joinSelectedCells) {
2518
0
    // We have selected cells: Join just contiguous cells
2519
0
    //  and just merge contents if not contiguous
2520
0
    TableSize tableSize(*this, *table, error);
2521
0
    if (NS_WARN_IF(error.Failed())) {
2522
0
      return error.StealNSResult();
2523
0
    }
2524
0
2525
0
    // Get spans for cell we will merge into
2526
0
    int32_t firstRowSpan, firstColSpan;
2527
0
    rv = GetCellSpansAt(table,
2528
0
                        firstSelectedCell.mIndexes.mRow,
2529
0
                        firstSelectedCell.mIndexes.mColumn,
2530
0
                        firstRowSpan, firstColSpan);
2531
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2532
0
      return rv;
2533
0
    }
2534
0
2535
0
    // This defines the last indexes along the "edges"
2536
0
    //  of the contiguous block of cells, telling us
2537
0
    //  that we can join adjacent cells to the block
2538
0
    // Start with same as the first values,
2539
0
    //  then expand as we find adjacent selected cells
2540
0
    int32_t lastRowIndex = firstSelectedCell.mIndexes.mRow;
2541
0
    int32_t lastColIndex = firstSelectedCell.mIndexes.mColumn;
2542
0
    int32_t rowIndex, colIndex;
2543
0
2544
0
    // First pass: Determine boundaries of contiguous rectangular block
2545
0
    //  that we will join into one cell,
2546
0
    //  favoring adjacent cells in the same row
2547
0
    for (rowIndex = firstSelectedCell.mIndexes.mRow;
2548
0
         rowIndex <= lastRowIndex;
2549
0
         rowIndex++) {
2550
0
      int32_t currentRowCount = tableSize.mRowCount;
2551
0
      // Be sure each row doesn't have rowspan errors
2552
0
      rv = FixBadRowSpan(table, rowIndex, tableSize.mRowCount);
2553
0
      NS_ENSURE_SUCCESS(rv, rv);
2554
0
      // Adjust rowcount by number of rows we removed
2555
0
      lastRowIndex -= currentRowCount - tableSize.mRowCount;
2556
0
2557
0
      bool cellFoundInRow = false;
2558
0
      bool lastRowIsSet = false;
2559
0
      int32_t lastColInRow = 0;
2560
0
      int32_t firstColInRow = firstSelectedCell.mIndexes.mColumn;
2561
0
      for (colIndex = firstSelectedCell.mIndexes.mColumn;
2562
0
           colIndex < tableSize.mColumnCount;
2563
0
           colIndex += std::max(actualColSpan2, 1)) {
2564
0
        rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
2565
0
                           &startRowIndex2, &startColIndex2,
2566
0
                           &rowSpan2, &colSpan2,
2567
0
                           &actualRowSpan2, &actualColSpan2, &isSelected2);
2568
0
        NS_ENSURE_SUCCESS(rv, rv);
2569
0
2570
0
        if (isSelected2) {
2571
0
          if (!cellFoundInRow) {
2572
0
            // We've just found the first selected cell in this row
2573
0
            firstColInRow = colIndex;
2574
0
          }
2575
0
          if (rowIndex > firstSelectedCell.mIndexes.mRow &&
2576
0
              firstColInRow != firstSelectedCell.mIndexes.mColumn) {
2577
0
            // We're in at least the second row,
2578
0
            // but left boundary is "ragged" (not the same as 1st row's start)
2579
0
            //Let's just end block on previous row
2580
0
            // and keep previous lastColIndex
2581
0
            //TODO: We could try to find the Maximum firstColInRow
2582
0
            //      so our block can still extend down more rows?
2583
0
            lastRowIndex = std::max(0,rowIndex - 1);
2584
0
            lastRowIsSet = true;
2585
0
            break;
2586
0
          }
2587
0
          // Save max selected column in this row, including extra colspan
2588
0
          lastColInRow = colIndex + (actualColSpan2-1);
2589
0
          cellFoundInRow = true;
2590
0
        } else if (cellFoundInRow) {
2591
0
          // No cell or not selected, but at least one cell in row was found
2592
0
          if (rowIndex > firstSelectedCell.mIndexes.mRow + 1 &&
2593
0
              colIndex <= lastColIndex) {
2594
0
            // Cell is in a column less than current right border in
2595
0
            //  the third or higher selected row, so stop block at the previous row
2596
0
            lastRowIndex = std::max(0,rowIndex - 1);
2597
0
            lastRowIsSet = true;
2598
0
          }
2599
0
          // We're done with this row
2600
0
          break;
2601
0
        }
2602
0
      } // End of column loop
2603
0
2604
0
      // Done with this row
2605
0
      if (cellFoundInRow) {
2606
0
        if (rowIndex == firstSelectedCell.mIndexes.mRow) {
2607
0
          // First row always initializes the right boundary
2608
0
          lastColIndex = lastColInRow;
2609
0
        }
2610
0
2611
0
        // If we didn't determine last row above...
2612
0
        if (!lastRowIsSet) {
2613
0
          if (colIndex < lastColIndex) {
2614
0
            // (don't think we ever get here?)
2615
0
            // Cell is in a column less than current right boundary,
2616
0
            //  so stop block at the previous row
2617
0
            lastRowIndex = std::max(0,rowIndex - 1);
2618
0
          } else {
2619
0
            // Go on to examine next row
2620
0
            lastRowIndex = rowIndex+1;
2621
0
          }
2622
0
        }
2623
0
        // Use the minimum col we found so far for right boundary
2624
0
        lastColIndex = std::min(lastColIndex, lastColInRow);
2625
0
      } else {
2626
0
        // No selected cells in this row -- stop at row above
2627
0
        //  and leave last column at its previous value
2628
0
        lastRowIndex = std::max(0,rowIndex - 1);
2629
0
      }
2630
0
    }
2631
0
2632
0
    // The list of cells we will delete after joining
2633
0
    nsTArray<RefPtr<Element>> deleteList;
2634
0
2635
0
    // 2nd pass: Do the joining and merging
2636
0
    for (rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
2637
0
      for (colIndex = 0;
2638
0
           colIndex < tableSize.mColumnCount;
2639
0
           colIndex += std::max(actualColSpan2, 1)) {
2640
0
        rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
2641
0
                           &startRowIndex2, &startColIndex2,
2642
0
                           &rowSpan2, &colSpan2,
2643
0
                           &actualRowSpan2, &actualColSpan2, &isSelected2);
2644
0
        NS_ENSURE_SUCCESS(rv, rv);
2645
0
2646
0
        // If this is 0, we are past last cell in row, so exit the loop
2647
0
        if (!actualColSpan2) {
2648
0
          break;
2649
0
        }
2650
0
2651
0
        // Merge only selected cells (skip cell we're merging into, of course)
2652
0
        if (isSelected2 && cell2 != firstSelectedCell.mElement) {
2653
0
          if (rowIndex >= firstSelectedCell.mIndexes.mRow &&
2654
0
              rowIndex <= lastRowIndex &&
2655
0
              colIndex >= firstSelectedCell.mIndexes.mColumn &&
2656
0
              colIndex <= lastColIndex) {
2657
0
            // We are within the join region
2658
0
            // Problem: It is very tricky to delete cells as we merge,
2659
0
            //  since that will upset the cellmap
2660
0
            //  Instead, build a list of cells to delete and do it later
2661
0
            NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above");
2662
0
2663
0
            if (actualColSpan2 > 1) {
2664
0
              //Check if cell "hangs" off the boundary because of colspan > 1
2665
0
              //  Use split methods to chop off excess
2666
0
              int32_t extraColSpan = (startColIndex2 + actualColSpan2) - (lastColIndex+1);
2667
0
              if ( extraColSpan > 0) {
2668
0
                rv = SplitCellIntoColumns(table, startRowIndex2, startColIndex2,
2669
0
                                          actualColSpan2 - extraColSpan,
2670
0
                                          extraColSpan, nullptr);
2671
0
                NS_ENSURE_SUCCESS(rv, rv);
2672
0
              }
2673
0
            }
2674
0
2675
0
            rv = MergeCells(firstSelectedCell.mElement, cell2, false);
2676
0
            if (NS_WARN_IF(NS_FAILED(rv))) {
2677
0
              return rv;
2678
0
            }
2679
0
2680
0
            // Add cell to list to delete
2681
0
            deleteList.AppendElement(cell2.get());
2682
0
          } else if (aMergeNonContiguousContents) {
2683
0
            // Cell is outside join region -- just merge the contents
2684
0
            rv = MergeCells(firstSelectedCell.mElement, cell2, false);
2685
0
            if (NS_WARN_IF(NS_FAILED(rv))) {
2686
0
              return rv;
2687
0
            }
2688
0
          }
2689
0
        }
2690
0
      }
2691
0
    }
2692
0
2693
0
    // All cell contents are merged. Delete the empty cells we accumulated
2694
0
    // Prevent rules testing until we're done
2695
0
    AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2696
0
                                        *this, EditSubAction::eDeleteNode,
2697
0
                                        nsIEditor::eNext);
2698
0
2699
0
    for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) {
2700
0
      RefPtr<Element> nodeToBeRemoved = deleteList[i];
2701
0
      if (nodeToBeRemoved) {
2702
0
        rv = DeleteNodeWithTransaction(*nodeToBeRemoved);
2703
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
2704
0
          return rv;
2705
0
        }
2706
0
      }
2707
0
    }
2708
0
    // Cleanup selection: remove ranges where cells were deleted
2709
0
    RefPtr<Selection> selection = GetSelection();
2710
0
    NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2711
0
2712
0
    uint32_t rangeCount = selection->RangeCount();
2713
0
2714
0
    RefPtr<nsRange> range;
2715
0
    for (uint32_t i = 0; i < rangeCount; i++) {
2716
0
      range = selection->GetRangeAt(i);
2717
0
      NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2718
0
2719
0
      RefPtr<Element> deletedCell;
2720
0
      HTMLEditor::GetCellFromRange(range, getter_AddRefs(deletedCell));
2721
0
      if (!deletedCell) {
2722
0
        selection->RemoveRange(*range, IgnoreErrors());
2723
0
        rangeCount--;
2724
0
        i--;
2725
0
      }
2726
0
    }
2727
0
2728
0
    // Set spans for the cell everything merged into
2729
0
    rv = SetRowSpan(firstSelectedCell.mElement,
2730
0
                    lastRowIndex - firstSelectedCell.mIndexes.mRow + 1);
2731
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2732
0
      return rv;
2733
0
    }
2734
0
    rv = SetColSpan(firstSelectedCell.mElement,
2735
0
                    lastColIndex - firstSelectedCell.mIndexes.mColumn + 1);
2736
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2737
0
      return rv;
2738
0
    }
2739
0
2740
0
    // Fixup disturbances in table layout
2741
0
    DebugOnly<nsresult> rv = NormalizeTable(*selection, *table);
2742
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to normalize the table");
2743
0
  } else {
2744
0
    // Joining with cell to the right -- get rowspan and colspan data of target cell
2745
0
    rv = GetCellDataAt(table, startRowIndex, startColIndex,
2746
0
                       getter_AddRefs(targetCell),
2747
0
                       &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2748
0
                       &actualRowSpan, &actualColSpan, &isSelected);
2749
0
    NS_ENSURE_SUCCESS(rv, rv);
2750
0
    NS_ENSURE_TRUE(targetCell, NS_ERROR_NULL_POINTER);
2751
0
2752
0
    // Get data for cell to the right
2753
0
    rv = GetCellDataAt(table, startRowIndex, startColIndex + actualColSpan,
2754
0
                       getter_AddRefs(cell2),
2755
0
                       &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2,
2756
0
                       &actualRowSpan2, &actualColSpan2, &isSelected2);
2757
0
    NS_ENSURE_SUCCESS(rv, rv);
2758
0
    if (!cell2) {
2759
0
      return NS_OK; // Don't fail if there's no cell
2760
0
    }
2761
0
2762
0
    // sanity check
2763
0
    NS_ASSERTION((startRowIndex >= startRowIndex2),"JoinCells: startRowIndex < startRowIndex2");
2764
0
2765
0
    // Figure out span of merged cell starting from target's starting row
2766
0
    // to handle case of merged cell starting in a row above
2767
0
    int32_t spanAboveMergedCell = startRowIndex - startRowIndex2;
2768
0
    int32_t effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell;
2769
0
2770
0
    if (effectiveRowSpan2 > actualRowSpan) {
2771
0
      // Cell to the right spans into row below target
2772
0
      // Split off portion below target cell's bottom-most row
2773
0
      rv = SplitCellIntoRows(table, startRowIndex2, startColIndex2,
2774
0
                             spanAboveMergedCell+actualRowSpan,
2775
0
                             effectiveRowSpan2-actualRowSpan, nullptr);
2776
0
      NS_ENSURE_SUCCESS(rv, rv);
2777
0
    }
2778
0
2779
0
    // Move contents from cell to the right
2780
0
    // Delete the cell now only if it starts in the same row
2781
0
    //   and has enough row "height"
2782
0
    rv = MergeCells(targetCell, cell2,
2783
0
                    (startRowIndex2 == startRowIndex) &&
2784
0
                    (effectiveRowSpan2 >= actualRowSpan));
2785
0
    NS_ENSURE_SUCCESS(rv, rv);
2786
0
2787
0
    if (effectiveRowSpan2 < actualRowSpan) {
2788
0
      // Merged cell is "shorter"
2789
0
      // (there are cells(s) below it that are row-spanned by target cell)
2790
0
      // We could try splitting those cells, but that's REAL messy,
2791
0
      //  so the safest thing to do is NOT really join the cells
2792
0
      return NS_OK;
2793
0
    }
2794
0
2795
0
    if (spanAboveMergedCell > 0) {
2796
0
      // Cell we merged started in a row above the target cell
2797
0
      // Reduce rowspan to give room where target cell will extend its colspan
2798
0
      rv = SetRowSpan(cell2, spanAboveMergedCell);
2799
0
      NS_ENSURE_SUCCESS(rv, rv);
2800
0
    }
2801
0
2802
0
    // Reset target cell's colspan to encompass cell to the right
2803
0
    rv = SetColSpan(targetCell, actualColSpan+actualColSpan2);
2804
0
    NS_ENSURE_SUCCESS(rv, rv);
2805
0
  }
2806
0
  return NS_OK;
2807
0
}
2808
2809
nsresult
2810
HTMLEditor::MergeCells(RefPtr<Element> aTargetCell,
2811
                       RefPtr<Element> aCellToMerge,
2812
                       bool aDeleteCellToMerge)
2813
0
{
2814
0
  NS_ENSURE_TRUE(aTargetCell && aCellToMerge, NS_ERROR_NULL_POINTER);
2815
0
2816
0
  // Prevent rules testing until we're done
2817
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2818
0
                                      *this, EditSubAction::eDeleteNode,
2819
0
                                      nsIEditor::eNext);
2820
0
2821
0
  // Don't need to merge if cell is empty
2822
0
  if (!IsEmptyCell(aCellToMerge)) {
2823
0
    // Get index of last child in target cell
2824
0
    // If we fail or don't have children,
2825
0
    //  we insert at index 0
2826
0
    int32_t insertIndex = 0;
2827
0
2828
0
    // Start inserting just after last child
2829
0
    uint32_t len = aTargetCell->GetChildCount();
2830
0
    if (len == 1 && IsEmptyCell(aTargetCell)) {
2831
0
      // Delete the empty node
2832
0
      nsIContent* cellChild = aTargetCell->GetFirstChild();
2833
0
      if (NS_WARN_IF(!cellChild)) {
2834
0
        return NS_ERROR_FAILURE;
2835
0
      }
2836
0
      nsresult rv = DeleteNodeWithTransaction(*cellChild);
2837
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
2838
0
        return rv;
2839
0
      }
2840
0
      insertIndex = 0;
2841
0
    } else {
2842
0
      insertIndex = (int32_t)len;
2843
0
    }
2844
0
2845
0
    // Move the contents
2846
0
    while (aCellToMerge->HasChildren()) {
2847
0
      nsCOMPtr<nsIContent> cellChild = aCellToMerge->GetLastChild();
2848
0
      if (NS_WARN_IF(!cellChild)) {
2849
0
        return NS_ERROR_FAILURE;
2850
0
      }
2851
0
      nsresult rv = DeleteNodeWithTransaction(*cellChild);
2852
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
2853
0
        return rv;
2854
0
      }
2855
0
      rv = InsertNodeWithTransaction(*cellChild,
2856
0
                                     EditorRawDOMPoint(aTargetCell,
2857
0
                                                       insertIndex));
2858
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
2859
0
        return rv;
2860
0
      }
2861
0
    }
2862
0
  }
2863
0
2864
0
  if (!aDeleteCellToMerge) {
2865
0
    return NS_OK;
2866
0
  }
2867
0
2868
0
  // Delete cells whose contents were moved.
2869
0
  nsresult rv = DeleteNodeWithTransaction(*aCellToMerge);
2870
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2871
0
    return rv;
2872
0
  }
2873
0
  return NS_OK;
2874
0
}
2875
2876
2877
nsresult
2878
HTMLEditor::FixBadRowSpan(Element* aTable,
2879
                          int32_t aRowIndex,
2880
                          int32_t& aNewRowCount)
2881
0
{
2882
0
  if (NS_WARN_IF(!aTable)) {
2883
0
    return NS_ERROR_INVALID_ARG;
2884
0
  }
2885
0
2886
0
  ErrorResult error;
2887
0
  TableSize tableSize(*this, *aTable, error);
2888
0
  if (NS_WARN_IF(error.Failed())) {
2889
0
    return error.StealNSResult();
2890
0
  }
2891
0
2892
0
  RefPtr<Element> cell;
2893
0
  int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2894
0
  bool    isSelected;
2895
0
2896
0
  int32_t minRowSpan = -1;
2897
0
  int32_t colIndex;
2898
0
2899
0
  for (colIndex = 0;
2900
0
       colIndex < tableSize.mColumnCount;
2901
0
       colIndex += std::max(actualColSpan, 1)) {
2902
0
    nsresult rv =
2903
0
      GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2904
0
                    &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2905
0
                    &actualRowSpan, &actualColSpan, &isSelected);
2906
0
    // NOTE: This is a *real* failure.
2907
0
    // GetCellDataAt passes if cell is missing from cellmap
2908
0
    // XXX If <table> has large rowspan value or colspan value than actual
2909
0
    //     cells, we may hit error.  So, this method is always failed to
2910
0
    //     "fix" the rowspan...
2911
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2912
0
      return rv;
2913
0
    }
2914
0
    if (!cell) {
2915
0
      break;
2916
0
    }
2917
0
    if (rowSpan > 0 &&
2918
0
        startRowIndex == aRowIndex &&
2919
0
        (rowSpan < minRowSpan || minRowSpan == -1)) {
2920
0
      minRowSpan = rowSpan;
2921
0
    }
2922
0
    NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
2923
0
  }
2924
0
  if (minRowSpan > 1) {
2925
0
    // The amount to reduce everyone's rowspan
2926
0
    // so at least one cell has rowspan = 1
2927
0
    int32_t rowsReduced = minRowSpan - 1;
2928
0
    for (colIndex = 0;
2929
0
         colIndex < tableSize.mColumnCount;
2930
0
         colIndex += std::max(actualColSpan, 1)) {
2931
0
      nsresult rv =
2932
0
        GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2933
0
                      &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2934
0
                      &actualRowSpan, &actualColSpan, &isSelected);
2935
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
2936
0
        return rv;
2937
0
      }
2938
0
      // Fixup rowspans only for cells starting in current row
2939
0
      if (cell && rowSpan > 0 &&
2940
0
          startRowIndex == aRowIndex &&
2941
0
          startColIndex ==  colIndex ) {
2942
0
        rv = SetRowSpan(cell, rowSpan-rowsReduced);
2943
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
2944
0
          return rv;
2945
0
        }
2946
0
      }
2947
0
      NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
2948
0
    }
2949
0
  }
2950
0
  tableSize.Update(*this, *aTable, error);
2951
0
  if (NS_WARN_IF(error.Failed())) {
2952
0
    return error.StealNSResult();
2953
0
  }
2954
0
  aNewRowCount = tableSize.mRowCount;
2955
0
  return NS_OK;
2956
0
}
2957
2958
nsresult
2959
HTMLEditor::FixBadColSpan(Element* aTable,
2960
                          int32_t aColIndex,
2961
                          int32_t& aNewColCount)
2962
0
{
2963
0
  if (NS_WARN_IF(!aTable)) {
2964
0
    return NS_ERROR_INVALID_ARG;
2965
0
  }
2966
0
2967
0
  ErrorResult error;
2968
0
  TableSize tableSize(*this, *aTable, error);
2969
0
  if (NS_WARN_IF(error.Failed())) {
2970
0
    return error.StealNSResult();
2971
0
  }
2972
0
2973
0
  RefPtr<Element> cell;
2974
0
  int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2975
0
  bool    isSelected;
2976
0
2977
0
  int32_t minColSpan = -1;
2978
0
  int32_t rowIndex;
2979
0
2980
0
  for (rowIndex = 0;
2981
0
       rowIndex < tableSize.mRowCount;
2982
0
       rowIndex += std::max(actualRowSpan, 1)) {
2983
0
    nsresult rv =
2984
0
      GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
2985
0
                    &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2986
0
                    &actualRowSpan, &actualColSpan, &isSelected);
2987
0
    // NOTE: This is a *real* failure.
2988
0
    // GetCellDataAt passes if cell is missing from cellmap
2989
0
    // XXX If <table> has large rowspan value or colspan value than actual
2990
0
    //     cells, we may hit error.  So, this method is always failed to
2991
0
    //     "fix" the colspan...
2992
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2993
0
      return rv;
2994
0
    }
2995
0
    if (!cell) {
2996
0
      break;
2997
0
    }
2998
0
    if (colSpan > 0 &&
2999
0
        startColIndex == aColIndex &&
3000
0
        (colSpan < minColSpan || minColSpan == -1)) {
3001
0
      minColSpan = colSpan;
3002
0
    }
3003
0
    NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
3004
0
  }
3005
0
  if (minColSpan > 1) {
3006
0
    // The amount to reduce everyone's colspan
3007
0
    // so at least one cell has colspan = 1
3008
0
    int32_t colsReduced = minColSpan - 1;
3009
0
    for (rowIndex = 0;
3010
0
         rowIndex < tableSize.mRowCount;
3011
0
         rowIndex += std::max(actualRowSpan, 1)) {
3012
0
      nsresult rv =
3013
0
        GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
3014
0
                      &startRowIndex, &startColIndex, &rowSpan, &colSpan,
3015
0
                      &actualRowSpan, &actualColSpan, &isSelected);
3016
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
3017
0
        return rv;
3018
0
      }
3019
0
      // Fixup colspans only for cells starting in current column
3020
0
      if (cell && colSpan > 0 &&
3021
0
          startColIndex == aColIndex &&
3022
0
          startRowIndex ==  rowIndex) {
3023
0
        rv = SetColSpan(cell, colSpan-colsReduced);
3024
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
3025
0
          return rv;
3026
0
        }
3027
0
      }
3028
0
      NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
3029
0
    }
3030
0
  }
3031
0
  tableSize.Update(*this, *aTable, error);
3032
0
  if (NS_WARN_IF(error.Failed())) {
3033
0
    return error.StealNSResult();
3034
0
  }
3035
0
  aNewColCount = tableSize.mColumnCount;
3036
0
  return NS_OK;
3037
0
}
3038
3039
NS_IMETHODIMP
3040
HTMLEditor::NormalizeTable(Element* aTableOrElementInTable)
3041
0
{
3042
0
  RefPtr<Selection> selection = GetSelection();
3043
0
  if (NS_WARN_IF(!selection)) {
3044
0
    return NS_ERROR_FAILURE;
3045
0
  }
3046
0
  if (!aTableOrElementInTable) {
3047
0
    aTableOrElementInTable =
3048
0
      GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
3049
0
    if (!aTableOrElementInTable) {
3050
0
      return NS_OK; // Don't throw error even if the element is not in <table>.
3051
0
    }
3052
0
  }
3053
0
  nsresult rv = NormalizeTable(*selection, *aTableOrElementInTable);
3054
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
3055
0
    return rv;
3056
0
  }
3057
0
  return NS_OK;
3058
0
}
3059
3060
nsresult
3061
HTMLEditor::NormalizeTable(Selection& aSelection,
3062
                           Element& aTableOrElementInTable)
3063
0
{
3064
0
3065
0
  RefPtr<Element> tableElement;
3066
0
  if (aTableOrElementInTable.NodeInfo()->NameAtom() == nsGkAtoms::table) {
3067
0
    tableElement = &aTableOrElementInTable;
3068
0
  } else {
3069
0
    tableElement =
3070
0
      GetElementOrParentByTagNameInternal(*nsGkAtoms::table,
3071
0
                                          aTableOrElementInTable);
3072
0
    if (!tableElement) {
3073
0
      return NS_OK; // Don't throw error even if the element is not in <table>.
3074
0
    }
3075
0
  }
3076
0
3077
0
  ErrorResult error;
3078
0
  TableSize tableSize(*this, *tableElement, error);
3079
0
  if (NS_WARN_IF(error.Failed())) {
3080
0
    return error.StealNSResult();
3081
0
  }
3082
0
3083
0
  // Save current selection
3084
0
  AutoSelectionRestorer selectionRestorer(&aSelection, this);
3085
0
3086
0
  AutoPlaceholderBatch beginBatching(this);
3087
0
  // Prevent auto insertion of BR in new cell until we're done
3088
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
3089
0
                                      *this, EditSubAction::eInsertNode,
3090
0
                                      nsIEditor::eNext);
3091
0
3092
0
  // XXX If there is a cell which has bigger or smaller "rowspan" or "colspan"
3093
0
  //     values, FixBadRowSpan() will return error.  So, we can do nothing
3094
0
  //     if the table needs normalization...
3095
0
  // Scan all cells in each row to detect bad rowspan values
3096
0
  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3097
0
    nsresult rv = FixBadRowSpan(tableElement, rowIndex, tableSize.mRowCount);
3098
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
3099
0
      return rv;
3100
0
    }
3101
0
  }
3102
0
  // and same for colspans
3103
0
  for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3104
0
    nsresult rv = FixBadColSpan(tableElement, colIndex, tableSize.mColumnCount);
3105
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
3106
0
      return rv;
3107
0
    }
3108
0
  }
3109
0
3110
0
  // Fill in missing cellmap locations with empty cells
3111
0
  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3112
0
    RefPtr<Element> previousCellElementInRow;
3113
0
    for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3114
0
      int32_t startRowIndex = 0, startColIndex = 0;
3115
0
      int32_t rowSpan = 0, colSpan = 0;
3116
0
      int32_t actualRowSpan = 0, actualColSpan = 0;
3117
0
      bool isSelected;
3118
0
      RefPtr<Element> cellElement;
3119
0
      nsresult rv =
3120
0
        GetCellDataAt(tableElement, rowIndex, colIndex,
3121
0
                      getter_AddRefs(cellElement),
3122
0
                      &startRowIndex, &startColIndex, &rowSpan, &colSpan,
3123
0
                      &actualRowSpan, &actualColSpan, &isSelected);
3124
0
      // NOTE: This is a *real* failure.
3125
0
      // GetCellDataAt passes if cell is missing from cellmap
3126
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
3127
0
        return rv;
3128
0
      }
3129
0
      if (!cellElement) {
3130
0
        // We are missing a cell at a cellmap location.
3131
0
        // Add a cell after the previous cell element in the current row.
3132
0
        if (NS_WARN_IF(!previousCellElementInRow)) {
3133
0
          // We don't have any cells in this row -- We are really messed up!
3134
0
          return NS_ERROR_FAILURE;
3135
0
        }
3136
0
3137
0
        // Insert a new cell after (true), and return the new cell to us
3138
0
        rv = InsertCell(previousCellElementInRow, 1, 1, true, false,
3139
0
                        getter_AddRefs(cellElement));
3140
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
3141
0
          return rv;
3142
0
        }
3143
0
3144
0
        // Set this so we use returned new "cell" to set
3145
0
        // previousCellElementInRow below.
3146
0
        if (cellElement) {
3147
0
          startRowIndex = rowIndex;
3148
0
        }
3149
0
      }
3150
0
      // Save the last cell found in the same row we are scanning
3151
0
      if (startRowIndex == rowIndex) {
3152
0
        previousCellElementInRow = cellElement;
3153
0
      }
3154
0
    }
3155
0
  }
3156
0
  return NS_OK;
3157
0
}
3158
3159
NS_IMETHODIMP
3160
HTMLEditor::GetCellIndexes(Element* aCellElement,
3161
                           int32_t* aRowIndex,
3162
                           int32_t* aColumnIndex)
3163
0
{
3164
0
  if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex)) {
3165
0
    return NS_ERROR_INVALID_ARG;
3166
0
  }
3167
0
  *aRowIndex = 0;
3168
0
  *aColumnIndex = 0;
3169
0
3170
0
  if (!aCellElement) {
3171
0
    // Use cell element which contains anchor of Selection when aCellElement is
3172
0
    // nullptr.
3173
0
    RefPtr<Selection> selection = GetSelection();
3174
0
    if (NS_WARN_IF(!selection)) {
3175
0
      return NS_ERROR_FAILURE;
3176
0
    }
3177
0
    ErrorResult error;
3178
0
    CellIndexes cellIndexes(*this, *selection, error);
3179
0
    if (error.Failed()) {
3180
0
      return error.StealNSResult();
3181
0
    }
3182
0
    *aRowIndex = cellIndexes.mRow;
3183
0
    *aColumnIndex = cellIndexes.mColumn;
3184
0
    return NS_OK;
3185
0
  }
3186
0
3187
0
  ErrorResult error;
3188
0
  CellIndexes cellIndexes(*aCellElement, error);
3189
0
  if (NS_WARN_IF(error.Failed())) {
3190
0
    return error.StealNSResult();
3191
0
  }
3192
0
  *aRowIndex = cellIndexes.mRow;
3193
0
  *aColumnIndex = cellIndexes.mColumn;
3194
0
  return NS_OK;
3195
0
}
3196
3197
void
3198
HTMLEditor::CellIndexes::Update(HTMLEditor& aHTMLEditor,
3199
                                Selection& aSelection,
3200
                                ErrorResult& aRv)
3201
0
{
3202
0
  MOZ_ASSERT(!aRv.Failed());
3203
0
3204
0
  // Guarantee the life time of the cell element since Init() will access
3205
0
  // layout methods.
3206
0
  RefPtr<Element> cellElement =
3207
0
    aHTMLEditor.GetElementOrParentByTagNameAtSelection(aSelection,
3208
0
                                                       *nsGkAtoms::td);
3209
0
  if (!cellElement) {
3210
0
    aRv.Throw(NS_ERROR_FAILURE);
3211
0
    return;
3212
0
  }
3213
0
  Update(*cellElement, aRv);
3214
0
}
3215
3216
void
3217
HTMLEditor::CellIndexes::Update(Element& aCellElement,
3218
                                ErrorResult& aRv)
3219
0
{
3220
0
  MOZ_ASSERT(!aRv.Failed());
3221
0
3222
0
  // XXX If the table cell is created immediately before this call, e.g.,
3223
0
  //     using innerHTML, frames have not been created yet.  In such case,
3224
0
  //     shouldn't we flush pending layout?
3225
0
  nsIFrame* frameOfCell = aCellElement.GetPrimaryFrame();
3226
0
  if (NS_WARN_IF(!frameOfCell)) {
3227
0
    aRv.Throw(NS_ERROR_FAILURE);
3228
0
    return;
3229
0
  }
3230
0
3231
0
  nsITableCellLayout* tableCellLayout = do_QueryFrame(frameOfCell);
3232
0
  if (!tableCellLayout) {
3233
0
    aRv.Throw(NS_ERROR_FAILURE); // not a cell element.
3234
0
    return;
3235
0
  }
3236
0
3237
0
  aRv = tableCellLayout->GetCellIndexes(mRow, mColumn);
3238
0
  NS_WARNING_ASSERTION(!aRv.Failed(), "Failed to get cell indexes");
3239
0
}
3240
3241
// static
3242
nsTableWrapperFrame*
3243
HTMLEditor::GetTableFrame(Element* aTableElement)
3244
0
{
3245
0
  if (NS_WARN_IF(!aTableElement)) {
3246
0
    return nullptr;
3247
0
  }
3248
0
  return do_QueryFrame(aTableElement->GetPrimaryFrame());
3249
0
}
3250
3251
//Return actual number of cells (a cell with colspan > 1 counts as just 1)
3252
int32_t
3253
HTMLEditor::GetNumberOfCellsInRow(Element& aTableElement,
3254
                                  int32_t aRowIndex)
3255
0
{
3256
0
  IgnoredErrorResult ignoredError;
3257
0
  TableSize tableSize(*this, aTableElement, ignoredError);
3258
0
  if (NS_WARN_IF(ignoredError.Failed())) {
3259
0
    return -1;
3260
0
  }
3261
0
3262
0
  int32_t numberOfCells = 0;
3263
0
  for (int32_t columnIndex = 0; columnIndex < tableSize.mColumnCount;) {
3264
0
    RefPtr<Element> cellElement;
3265
0
    int32_t startRowIndex = 0, startColIndex = 0;
3266
0
    int32_t rowSpan = 0, colSpan = 0;
3267
0
    int32_t actualRowSpan = 0, actualColSpan = 0;
3268
0
    bool isSelected = false;
3269
0
    nsresult rv =
3270
0
      GetCellDataAt(&aTableElement, aRowIndex, columnIndex,
3271
0
                    getter_AddRefs(cellElement),
3272
0
                    &startRowIndex, &startColIndex, &rowSpan, &colSpan,
3273
0
                    &actualRowSpan, &actualColSpan, &isSelected);
3274
0
    // Failure means that there is no more cell in the row.  In this case,
3275
0
    // we shouldn't return error since we just reach the end of the row.
3276
0
    // XXX Ideally, GetCellDataAt() should return
3277
0
    //     NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND in such case instead of
3278
0
    //     error.  However, it's used by a lot of methods, so, it's really
3279
0
    //     risky to change it.
3280
0
    if (NS_FAILED(rv)) {
3281
0
      break;
3282
0
    }
3283
0
    if (cellElement) {
3284
0
      // Only count cells that start in row we are working with
3285
0
      if (startRowIndex == aRowIndex) {
3286
0
        numberOfCells++;
3287
0
      }
3288
0
      // Next possible location for a cell
3289
0
      columnIndex += actualColSpan;
3290
0
    } else {
3291
0
      columnIndex++;
3292
0
    }
3293
0
  }
3294
0
  return numberOfCells;
3295
0
}
3296
3297
NS_IMETHODIMP
3298
HTMLEditor::GetTableSize(Element* aTableOrElementInTable,
3299
                         int32_t* aRowCount,
3300
                         int32_t* aColumnCount)
3301
0
{
3302
0
  if (NS_WARN_IF(!aRowCount) || NS_WARN_IF(!aColumnCount)) {
3303
0
    return NS_ERROR_INVALID_ARG;
3304
0
  }
3305
0
3306
0
  *aRowCount = 0;
3307
0
  *aColumnCount = 0;
3308
0
3309
0
  Element* tableOrElementInTable = aTableOrElementInTable;
3310
0
  if (!tableOrElementInTable) {
3311
0
    RefPtr<Selection> selection = GetSelection();
3312
0
    if (NS_WARN_IF(!selection)) {
3313
0
      return NS_ERROR_FAILURE;
3314
0
    }
3315
0
    tableOrElementInTable =
3316
0
      GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
3317
0
    if (NS_WARN_IF(!tableOrElementInTable)) {
3318
0
      return NS_ERROR_FAILURE;
3319
0
    }
3320
0
  }
3321
0
3322
0
  ErrorResult error;
3323
0
  TableSize tableSize(*this, *tableOrElementInTable, error);
3324
0
  if (NS_WARN_IF(error.Failed())) {
3325
0
    return error.StealNSResult();
3326
0
  }
3327
0
  *aRowCount = tableSize.mRowCount;
3328
0
  *aColumnCount = tableSize.mColumnCount;
3329
0
  return NS_OK;
3330
0
}
3331
3332
void
3333
HTMLEditor::TableSize::Update(HTMLEditor& aHTMLEditor,
3334
                              Element& aTableOrElementInTable,
3335
                              ErrorResult& aRv)
3336
0
{
3337
0
  MOZ_ASSERT(!aRv.Failed());
3338
0
3339
0
  // Currently, nsTableWrapperFrame::GetRowCount() and
3340
0
  // nsTableWrapperFrame::GetColCount() are safe to use without grabbing
3341
0
  // <table> element.  However, editor developers may not watch layout API
3342
0
  // changes.  So, for keeping us safer, we should use RefPtr here.
3343
0
  RefPtr<Element> tableElement =
3344
0
    aHTMLEditor.GetElementOrParentByTagNameInternal(*nsGkAtoms::table,
3345
0
                                                    aTableOrElementInTable);
3346
0
  if (NS_WARN_IF(!tableElement)) {
3347
0
    aRv.Throw(NS_ERROR_FAILURE);
3348
0
    return;
3349
0
  }
3350
0
  nsTableWrapperFrame* tableFrame =
3351
0
    do_QueryFrame(tableElement->GetPrimaryFrame());
3352
0
  if (NS_WARN_IF(!tableFrame)) {
3353
0
    aRv.Throw(NS_ERROR_FAILURE);
3354
0
    return;
3355
0
  }
3356
0
  mRowCount = tableFrame->GetRowCount();
3357
0
  mColumnCount = tableFrame->GetColCount();
3358
0
}
3359
3360
NS_IMETHODIMP
3361
HTMLEditor::GetCellDataAt(Element* aTable,
3362
                          int32_t aRowIndex,
3363
                          int32_t aColIndex,
3364
                          Element** aCell,
3365
                          int32_t* aStartRowIndex,
3366
                          int32_t* aStartColIndex,
3367
                          int32_t* aRowSpan,
3368
                          int32_t* aColSpan,
3369
                          int32_t* aActualRowSpan,
3370
                          int32_t* aActualColSpan,
3371
                          bool* aIsSelected)
3372
0
{
3373
0
  NS_ENSURE_ARG_POINTER(aStartRowIndex);
3374
0
  NS_ENSURE_ARG_POINTER(aStartColIndex);
3375
0
  NS_ENSURE_ARG_POINTER(aRowSpan);
3376
0
  NS_ENSURE_ARG_POINTER(aColSpan);
3377
0
  NS_ENSURE_ARG_POINTER(aActualRowSpan);
3378
0
  NS_ENSURE_ARG_POINTER(aActualColSpan);
3379
0
  NS_ENSURE_ARG_POINTER(aIsSelected);
3380
0
  NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
3381
0
3382
0
  *aStartRowIndex = 0;
3383
0
  *aStartColIndex = 0;
3384
0
  *aRowSpan = 0;
3385
0
  *aColSpan = 0;
3386
0
  *aActualRowSpan = 0;
3387
0
  *aActualColSpan = 0;
3388
0
  *aIsSelected = false;
3389
0
3390
0
  *aCell = nullptr;
3391
0
3392
0
  // needs to live while we use aTable
3393
0
  // XXX Really? Looks like it's safe to use raw pointer here.
3394
0
  //     However, layout code change won't be handled by editor developers
3395
0
  //     so that it must be safe to keep using RefPtr here.
3396
0
  RefPtr<Element> table;
3397
0
  if (!aTable) {
3398
0
    RefPtr<Selection> selection = GetSelection();
3399
0
    if (NS_WARN_IF(!selection)) {
3400
0
      return NS_ERROR_FAILURE;
3401
0
    }
3402
0
    // Get the selected table or the table enclosing the selection anchor.
3403
0
    table =
3404
0
      GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
3405
0
    if (NS_WARN_IF(!table)) {
3406
0
      return NS_ERROR_FAILURE;
3407
0
    }
3408
0
    aTable = table;
3409
0
  }
3410
0
3411
0
  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(aTable);
3412
0
  NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE);
3413
0
3414
0
  nsTableCellFrame* cellFrame =
3415
0
    tableFrame->GetCellFrameAt(aRowIndex, aColIndex);
3416
0
  if (NS_WARN_IF(!cellFrame)) {
3417
0
    return NS_ERROR_FAILURE;
3418
0
  }
3419
0
3420
0
  *aIsSelected = cellFrame->IsSelected();
3421
0
  *aStartRowIndex = cellFrame->RowIndex();
3422
0
  *aStartColIndex = cellFrame->ColIndex();
3423
0
  *aRowSpan = cellFrame->GetRowSpan();
3424
0
  *aColSpan = cellFrame->GetColSpan();
3425
0
  *aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
3426
0
  *aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
3427
0
  RefPtr<Element> domCell = cellFrame->GetContent()->AsElement();
3428
0
  domCell.forget(aCell);
3429
0
3430
0
  return NS_OK;
3431
0
}
3432
3433
NS_IMETHODIMP
3434
HTMLEditor::GetCellAt(Element* aTableElement,
3435
                      int32_t aRowIndex,
3436
                      int32_t aColumnIndex,
3437
                      Element** aCellElement)
3438
0
{
3439
0
  if (NS_WARN_IF(!aCellElement)) {
3440
0
    return NS_ERROR_INVALID_ARG;
3441
0
  }
3442
0
3443
0
  *aCellElement = nullptr;
3444
0
3445
0
  Element* tableElement = aTableElement;
3446
0
  if (!tableElement) {
3447
0
    RefPtr<Selection> selection = GetSelection();
3448
0
    if (NS_WARN_IF(!selection)) {
3449
0
      return NS_ERROR_FAILURE;
3450
0
    }
3451
0
    // Get the selected table or the table enclosing the selection anchor.
3452
0
    tableElement =
3453
0
      GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
3454
0
    if (NS_WARN_IF(!tableElement)) {
3455
0
      return NS_ERROR_FAILURE;
3456
0
    }
3457
0
  }
3458
0
3459
0
  RefPtr<Element> cellElement =
3460
0
    GetTableCellElementAt(*tableElement, aRowIndex, aColumnIndex);
3461
0
  cellElement.forget(aCellElement);
3462
0
  return NS_OK;
3463
0
}
3464
3465
Element*
3466
HTMLEditor::GetTableCellElementAt(Element& aTableElement,
3467
                                  int32_t aRowIndex,
3468
                                  int32_t aColumnIndex) const
3469
0
{
3470
0
  // Let's grab the <table> element while we're retrieving layout API since
3471
0
  // editor developers do not watch all layout API changes.  So, it may
3472
0
  // become unsafe.
3473
0
  OwningNonNull<Element> tableElement(aTableElement);
3474
0
  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(tableElement);
3475
0
  if (!tableFrame) {
3476
0
    return nullptr;
3477
0
  }
3478
0
  nsIContent* cell = tableFrame->GetCellAt(aRowIndex, aColumnIndex);
3479
0
  return Element::FromNodeOrNull(cell);
3480
0
}
3481
3482
// When all you want are the rowspan and colspan (not exposed in nsITableEditor)
3483
nsresult
3484
HTMLEditor::GetCellSpansAt(Element* aTable,
3485
                           int32_t aRowIndex,
3486
                           int32_t aColIndex,
3487
                           int32_t& aActualRowSpan,
3488
                           int32_t& aActualColSpan)
3489
0
{
3490
0
  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(aTable);
3491
0
  if (!tableFrame) {
3492
0
    return NS_ERROR_FAILURE;
3493
0
  }
3494
0
  aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
3495
0
  aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
3496
0
3497
0
  return NS_OK;
3498
0
}
3499
3500
nsresult
3501
HTMLEditor::GetCellContext(Selection** aSelection,
3502
                           Element** aTable,
3503
                           Element** aCell,
3504
                           nsINode** aCellParent,
3505
                           int32_t* aCellOffset,
3506
                           int32_t* aRowIndex,
3507
                           int32_t* aColumnIndex)
3508
0
{
3509
0
  // Initialize return pointers
3510
0
  if (aSelection) {
3511
0
    *aSelection = nullptr;
3512
0
  }
3513
0
  if (aTable) {
3514
0
    *aTable = nullptr;
3515
0
  }
3516
0
  if (aCell) {
3517
0
    *aCell = nullptr;
3518
0
  }
3519
0
  if (aCellParent) {
3520
0
    *aCellParent = nullptr;
3521
0
  }
3522
0
  if (aCellOffset) {
3523
0
    *aCellOffset = 0;
3524
0
  }
3525
0
  if (aRowIndex) {
3526
0
    *aRowIndex = 0;
3527
0
  }
3528
0
  if (aColumnIndex) {
3529
0
    *aColumnIndex = 0;
3530
0
  }
3531
0
3532
0
  RefPtr<Selection> selection = GetSelection();
3533
0
  if (NS_WARN_IF(!selection)) {
3534
0
    return NS_ERROR_FAILURE;
3535
0
  }
3536
0
3537
0
  if (aSelection) {
3538
0
    *aSelection = selection.get();
3539
0
    NS_ADDREF(*aSelection);
3540
0
  }
3541
0
  RefPtr<Element> table;
3542
0
  RefPtr<Element> cell;
3543
0
3544
0
  // Caller may supply the cell...
3545
0
  if (aCell && *aCell) {
3546
0
    cell = *aCell;
3547
0
  }
3548
0
3549
0
  // ...but if not supplied,
3550
0
  //    get cell if it's the child of selection anchor node,
3551
0
  //    or get the enclosing by a cell
3552
0
  if (!cell) {
3553
0
    // Find a selected or enclosing table element
3554
0
    ErrorResult error;
3555
0
    RefPtr<Element> cellOrRowOrTableElement =
3556
0
      GetSelectedOrParentTableElement(*selection, error);
3557
0
    if (NS_WARN_IF(error.Failed())) {
3558
0
      return error.StealNSResult();
3559
0
    }
3560
0
    if (!cellOrRowOrTableElement) {
3561
0
      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3562
0
    }
3563
0
    if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::table)) {
3564
0
      // We have a selected table, not a cell
3565
0
      if (aTable) {
3566
0
        cellOrRowOrTableElement.forget(aTable);
3567
0
      }
3568
0
      return NS_OK;
3569
0
    }
3570
0
    if (!cellOrRowOrTableElement->IsAnyOfHTMLElements(nsGkAtoms::td,
3571
0
                                                      nsGkAtoms::th)) {
3572
0
      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3573
0
    }
3574
0
3575
0
    // We found a cell
3576
0
    cell = std::move(cellOrRowOrTableElement);
3577
0
  }
3578
0
  if (aCell) {
3579
0
    // we don't want to cell.forget() here, because we use it below.
3580
0
    *aCell = do_AddRef(cell).take();
3581
0
  }
3582
0
3583
0
  // Get containing table
3584
0
  table = GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *cell);
3585
0
  if (NS_WARN_IF(!table)) {
3586
0
    // Cell must be in a table, so fail if not found
3587
0
    return NS_ERROR_FAILURE;
3588
0
  }
3589
0
  if (aTable) {
3590
0
    table.forget(aTable);
3591
0
  }
3592
0
3593
0
  // Get the rest of the related data only if requested
3594
0
  if (aRowIndex || aColumnIndex) {
3595
0
    ErrorResult error;
3596
0
    CellIndexes cellIndexes(*cell, error);
3597
0
    if (NS_WARN_IF(error.Failed())) {
3598
0
      return error.StealNSResult();
3599
0
    }
3600
0
    if (aRowIndex) {
3601
0
      *aRowIndex = cellIndexes.mRow;
3602
0
    }
3603
0
    if (aColumnIndex) {
3604
0
      *aColumnIndex = cellIndexes.mColumn;
3605
0
    }
3606
0
  }
3607
0
  if (aCellParent) {
3608
0
    // Get the immediate parent of the cell
3609
0
    nsCOMPtr<nsINode> cellParent = cell->GetParentNode();
3610
0
    // Cell has to have a parent, so fail if not found
3611
0
    NS_ENSURE_TRUE(cellParent, NS_ERROR_FAILURE);
3612
0
3613
0
    if (aCellOffset) {
3614
0
      *aCellOffset = GetChildOffset(cell, cellParent);
3615
0
    }
3616
0
3617
0
    // Now it's safe to hand over the reference to cellParent, since
3618
0
    // we don't need it anymore.
3619
0
    cellParent.forget(aCellParent);
3620
0
  }
3621
0
3622
0
  return NS_OK;
3623
0
}
3624
3625
// static
3626
nsresult
3627
HTMLEditor::GetCellFromRange(nsRange* aRange,
3628
                             Element** aCell)
3629
0
{
3630
0
  // Note: this might return a node that is outside of the range.
3631
0
  // Use carefully.
3632
0
  NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER);
3633
0
3634
0
  *aCell = nullptr;
3635
0
3636
0
  nsCOMPtr<nsINode> startContainer = aRange->GetStartContainer();
3637
0
  if (NS_WARN_IF(!startContainer)) {
3638
0
    return NS_ERROR_FAILURE;
3639
0
  }
3640
0
3641
0
  uint32_t startOffset = aRange->StartOffset();
3642
0
3643
0
  nsCOMPtr<nsINode> childNode = aRange->GetChildAtStartOffset();
3644
0
  // This means selection is probably at a text node (or end of doc?)
3645
0
  if (!childNode) {
3646
0
    return NS_ERROR_FAILURE;
3647
0
  }
3648
0
3649
0
  nsCOMPtr<nsINode> endContainer = aRange->GetEndContainer();
3650
0
  if (NS_WARN_IF(!endContainer)) {
3651
0
    return NS_ERROR_FAILURE;
3652
0
  }
3653
0
3654
0
  // If a cell is deleted, the range is collapse
3655
0
  //   (startOffset == aRange->EndOffset())
3656
0
  //   so tell caller the cell wasn't found
3657
0
  if (startContainer == endContainer &&
3658
0
      aRange->EndOffset() == startOffset+1 &&
3659
0
      HTMLEditUtils::IsTableCell(childNode)) {
3660
0
    // Should we also test if frame is selected? (Use GetCellDataAt())
3661
0
    // (Let's not for now -- more efficient)
3662
0
    RefPtr<Element> cellElement = childNode->AsElement();
3663
0
    cellElement.forget(aCell);
3664
0
    return NS_OK;
3665
0
  }
3666
0
  return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3667
0
}
3668
3669
NS_IMETHODIMP
3670
HTMLEditor::GetFirstSelectedCell(nsRange** aFirstSelectedRange,
3671
                                 Element** aFirstSelectedCellElement)
3672
0
{
3673
0
  if (NS_WARN_IF(!aFirstSelectedCellElement)) {
3674
0
    return NS_ERROR_INVALID_ARG;
3675
0
  }
3676
0
3677
0
  *aFirstSelectedCellElement = nullptr;
3678
0
  if (aFirstSelectedRange) {
3679
0
    *aFirstSelectedRange = nullptr;
3680
0
  }
3681
0
3682
0
  // XXX Oddly, when there is no ranges of Selection, we return error.
3683
0
  //     However, despite of that we return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
3684
0
  //     when first range of Selection does not select a table cell element.
3685
0
  RefPtr<Selection> selection = GetSelection();
3686
0
  if (NS_WARN_IF(!selection)) {
3687
0
    return NS_ERROR_FAILURE;
3688
0
  }
3689
0
3690
0
  ErrorResult error;
3691
0
  RefPtr<Element> firstSelectedCellElement =
3692
0
    GetFirstSelectedTableCellElement(*selection, error);
3693
0
  if (NS_WARN_IF(error.Failed())) {
3694
0
    return error.StealNSResult();
3695
0
  }
3696
0
3697
0
  if (!firstSelectedCellElement) {
3698
0
    // Just not found.  Don't return error.
3699
0
    return NS_OK;
3700
0
  }
3701
0
  firstSelectedCellElement.forget(aFirstSelectedCellElement);
3702
0
3703
0
  if (aFirstSelectedRange) {
3704
0
    // Returns the first range only when the caller requested the range.
3705
0
    RefPtr<nsRange> firstRange = selection->GetRangeAt(0);
3706
0
    MOZ_ASSERT(firstRange);
3707
0
    firstRange.forget(aFirstSelectedRange);
3708
0
  }
3709
0
3710
0
  return NS_OK;
3711
0
}
3712
3713
already_AddRefed<Element>
3714
HTMLEditor::GetFirstSelectedTableCellElement(Selection& aSelection,
3715
                                             ErrorResult& aRv) const
3716
0
{
3717
0
  MOZ_ASSERT(!aRv.Failed());
3718
0
3719
0
  nsRange* firstRange = aSelection.GetRangeAt(0);
3720
0
  if (NS_WARN_IF(!firstRange)) {
3721
0
    // XXX Why don't we treat "not found" in this case?
3722
0
    aRv.Throw(NS_ERROR_FAILURE);
3723
0
    return nullptr;
3724
0
  }
3725
0
3726
0
  // XXX It must be unclear when this is reset...
3727
0
  mSelectedCellIndex = 0;
3728
0
3729
0
  RefPtr<Element> selectedCell;
3730
0
  nsresult rv =
3731
0
    HTMLEditor::GetCellFromRange(firstRange, getter_AddRefs(selectedCell));
3732
0
  if (NS_FAILED(rv)) {
3733
0
    // This case occurs only when Selection is in a text node in normal cases.
3734
0
    return nullptr;
3735
0
  }
3736
0
  if (!selectedCell) {
3737
0
    // This case means that the range does not select only a cell.
3738
0
    // E.g., selects non-table cell element, selects two or more cells, or
3739
0
    //       does not select any cell element.
3740
0
    return nullptr;
3741
0
  }
3742
0
3743
0
  // Setup for GetNextSelectedTableCellElement()
3744
0
  // XXX Oh, increment it now?  Rather than when
3745
0
  //     GetNextSelectedTableCellElement() is called?
3746
0
  mSelectedCellIndex = 1;
3747
0
3748
0
  return selectedCell.forget();
3749
0
}
3750
3751
NS_IMETHODIMP
3752
HTMLEditor::GetNextSelectedCell(nsRange** aNextSelectedCellRange,
3753
                                Element** aNextSelectedCellElement)
3754
0
{
3755
0
  if (NS_WARN_IF(!aNextSelectedCellElement)) {
3756
0
    return NS_ERROR_INVALID_ARG;
3757
0
  }
3758
0
3759
0
  *aNextSelectedCellElement = nullptr;
3760
0
  if (aNextSelectedCellRange) {
3761
0
    *aNextSelectedCellRange = nullptr;
3762
0
  }
3763
0
3764
0
  RefPtr<Selection> selection = GetSelection();
3765
0
  if (NS_WARN_IF(!selection)) {
3766
0
    return NS_ERROR_FAILURE;
3767
0
  }
3768
0
3769
0
  ErrorResult error;
3770
0
  RefPtr<Element> nextSelectedCellElement =
3771
0
    GetNextSelectedTableCellElement(*selection, error);
3772
0
  if (NS_WARN_IF(error.Failed())) {
3773
0
    return error.StealNSResult();
3774
0
  }
3775
0
3776
0
  if (!nextSelectedCellElement) {
3777
0
    // not more range, or met a range which does not select <td> nor <th>.
3778
0
    return NS_OK;
3779
0
  }
3780
0
3781
0
  if (aNextSelectedCellRange) {
3782
0
    MOZ_ASSERT(mSelectedCellIndex > 0);
3783
0
    *aNextSelectedCellRange =
3784
0
      do_AddRef(selection->GetRangeAt(mSelectedCellIndex - 1)).take();
3785
0
  }
3786
0
  nextSelectedCellElement.forget(aNextSelectedCellElement);
3787
0
  return NS_OK;
3788
0
}
3789
3790
already_AddRefed<Element>
3791
HTMLEditor::GetNextSelectedTableCellElement(Selection& aSelection,
3792
                                            ErrorResult& aRv) const
3793
0
{
3794
0
  MOZ_ASSERT(!aRv.Failed());
3795
0
3796
0
  if (mSelectedCellIndex >= aSelection.RangeCount()) {
3797
0
    // We've already returned all selected cells.
3798
0
    return nullptr;
3799
0
  }
3800
0
3801
0
  MOZ_ASSERT(mSelectedCellIndex > 0);
3802
0
  for (; mSelectedCellIndex < aSelection.RangeCount(); mSelectedCellIndex++) {
3803
0
    nsRange* range = aSelection.GetRangeAt(mSelectedCellIndex);
3804
0
    if (NS_WARN_IF(!range)) {
3805
0
      aRv.Throw(NS_ERROR_FAILURE);
3806
0
      return nullptr;
3807
0
    }
3808
0
3809
0
    RefPtr<Element> nextSelectedCellElement;
3810
0
    nsresult rv =
3811
0
      HTMLEditor::GetCellFromRange(range,
3812
0
                                   getter_AddRefs(nextSelectedCellElement));
3813
0
    if (NS_FAILED(rv)) {
3814
0
      // Failure means that the range is in non-element node, e.g., a text node.
3815
0
      // Returns nullptr without error if not found.
3816
0
      // XXX Why don't we just skip such range or incrementing
3817
0
      //     mSelectedCellIndex for next call?
3818
0
      return nullptr;
3819
0
    }
3820
0
3821
0
    if (nextSelectedCellElement) {
3822
0
      mSelectedCellIndex++;
3823
0
      return nextSelectedCellElement.forget();
3824
0
    }
3825
0
  }
3826
0
3827
0
  // Returns nullptr without error if not found.
3828
0
  return nullptr;
3829
0
}
3830
3831
NS_IMETHODIMP
3832
HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex,
3833
                                        int32_t* aColumnIndex,
3834
                                        Element** aCellElement)
3835
0
{
3836
0
  if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex) ||
3837
0
      NS_WARN_IF(!aCellElement)) {
3838
0
    return NS_ERROR_INVALID_ARG;
3839
0
  }
3840
0
  
3841
0
3842
0
  *aRowIndex = 0;
3843
0
  *aColumnIndex = 0;
3844
0
  *aCellElement = nullptr;
3845
0
3846
0
  RefPtr<Selection> selection = GetSelection();
3847
0
  if (NS_WARN_IF(!selection)) {
3848
0
    return NS_ERROR_FAILURE;
3849
0
  }
3850
0
3851
0
  ErrorResult error;
3852
0
  CellAndIndexes result(*this, *selection, error);
3853
0
  if (NS_WARN_IF(error.Failed())) {
3854
0
    return error.StealNSResult();
3855
0
  }
3856
0
  result.mElement.forget(aCellElement);
3857
0
  *aRowIndex = std::max(result.mIndexes.mRow, 0);
3858
0
  *aColumnIndex = std::max(result.mIndexes.mColumn, 0);
3859
0
  return NS_OK;
3860
0
}
3861
3862
void
3863
HTMLEditor::CellAndIndexes::Update(HTMLEditor& aHTMLEditor,
3864
                                   Selection& aSelection,
3865
                                   ErrorResult& aRv)
3866
0
{
3867
0
  MOZ_ASSERT(!aRv.Failed());
3868
0
3869
0
  mIndexes.mRow = -1;
3870
0
  mIndexes.mColumn = -1;
3871
0
3872
0
  mElement = aHTMLEditor.GetFirstSelectedTableCellElement(aSelection, aRv);
3873
0
  if (NS_WARN_IF(aRv.Failed())) {
3874
0
    return;
3875
0
  }
3876
0
  if (!mElement) {
3877
0
    return;
3878
0
  }
3879
0
3880
0
  mIndexes.Update(*mElement, aRv);
3881
0
  NS_WARNING_ASSERTION(!aRv.Failed(),
3882
0
    "Selected element is found, but failed to compute its indexes");
3883
0
}
3884
3885
void
3886
HTMLEditor::SetSelectionAfterTableEdit(Element* aTable,
3887
                                       int32_t aRow,
3888
                                       int32_t aCol,
3889
                                       int32_t aDirection,
3890
                                       bool aSelected)
3891
0
{
3892
0
  if (NS_WARN_IF(!aTable) || Destroyed()) {
3893
0
    return;
3894
0
  }
3895
0
3896
0
  RefPtr<Selection> selection = GetSelection();
3897
0
  if (!selection) {
3898
0
    return;
3899
0
  }
3900
0
3901
0
  RefPtr<Element> cell;
3902
0
  bool done = false;
3903
0
  do {
3904
0
    cell = GetTableCellElementAt(*aTable, aRow, aCol);
3905
0
    if (cell) {
3906
0
      if (aSelected) {
3907
0
        // Reselect the cell
3908
0
        DebugOnly<nsresult> rv = SelectContentInternal(*selection, *cell);
3909
0
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3910
0
          "Failed to select the cell");
3911
0
        return;
3912
0
      }
3913
0
3914
0
      // Set the caret to deepest first child
3915
0
      //   but don't go into nested tables
3916
0
      // TODO: Should we really be placing the caret at the END
3917
0
      //  of the cell content?
3918
0
      CollapseSelectionToDeepestNonTableFirstChild(selection, cell);
3919
0
      return;
3920
0
    }
3921
0
3922
0
    // Setup index to find another cell in the
3923
0
    //   direction requested, but move in other direction if already at
3924
0
    //   beginning of row or column
3925
0
    switch (aDirection) {
3926
0
      case ePreviousColumn:
3927
0
        if (!aCol) {
3928
0
          if (aRow > 0) {
3929
0
            aRow--;
3930
0
          } else {
3931
0
            done = true;
3932
0
          }
3933
0
        } else {
3934
0
          aCol--;
3935
0
        }
3936
0
        break;
3937
0
      case ePreviousRow:
3938
0
        if (!aRow) {
3939
0
          if (aCol > 0) {
3940
0
            aCol--;
3941
0
          } else {
3942
0
            done = true;
3943
0
          }
3944
0
        } else {
3945
0
          aRow--;
3946
0
        }
3947
0
        break;
3948
0
      default:
3949
0
        done = true;
3950
0
    }
3951
0
  } while (!done);
3952
0
3953
0
  // We didn't find a cell
3954
0
  // Set selection to just before the table
3955
0
  if (aTable->GetParentNode()) {
3956
0
    EditorRawDOMPoint atTable(aTable);
3957
0
    if (NS_WARN_IF(!atTable.IsSetAndValid())) {
3958
0
      return;
3959
0
    }
3960
0
    selection->Collapse(atTable);
3961
0
    return;
3962
0
  }
3963
0
  // Last resort: Set selection to start of doc
3964
0
  // (it's very bad to not have a valid selection!)
3965
0
  SetSelectionAtDocumentStart(selection);
3966
0
}
3967
3968
NS_IMETHODIMP
3969
HTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName,
3970
                                            int32_t* aSelectedCount,
3971
                                            Element** aCellOrRowOrTableElement)
3972
0
{
3973
0
  if (NS_WARN_IF(!aSelectedCount) || NS_WARN_IF(!aCellOrRowOrTableElement)) {
3974
0
    return NS_ERROR_INVALID_ARG;
3975
0
  }
3976
0
3977
0
  aTagName.Truncate();
3978
0
  *aCellOrRowOrTableElement = nullptr;
3979
0
  *aSelectedCount = 0;
3980
0
3981
0
  RefPtr<Selection> selection = GetSelection();
3982
0
  if (NS_WARN_IF(!selection)) {
3983
0
    return NS_ERROR_FAILURE;
3984
0
  }
3985
0
3986
0
  bool isCellSelected = false;
3987
0
  ErrorResult aRv;
3988
0
  RefPtr<Element> cellOrRowOrTableElement =
3989
0
    GetSelectedOrParentTableElement(*selection, aRv, &isCellSelected);
3990
0
  if (NS_WARN_IF(aRv.Failed())) {
3991
0
    return aRv.StealNSResult();
3992
0
  }
3993
0
  if (!cellOrRowOrTableElement) {
3994
0
    return NS_OK;
3995
0
  }
3996
0
3997
0
  if (isCellSelected) {
3998
0
    aTagName.AssignLiteral("td");
3999
0
    *aSelectedCount = selection->RangeCount();
4000
0
    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4001
0
    return NS_OK;
4002
0
  }
4003
0
4004
0
  if (cellOrRowOrTableElement->IsAnyOfHTMLElements(nsGkAtoms::td,
4005
0
                                                   nsGkAtoms::th)) {
4006
0
    aTagName.AssignLiteral("td");
4007
0
    // Keep *aSelectedCount as 0.
4008
0
    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4009
0
    return NS_OK;
4010
0
  }
4011
0
4012
0
  if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::table)) {
4013
0
    aTagName.AssignLiteral("table");
4014
0
    *aSelectedCount = 1;
4015
0
    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4016
0
    return NS_OK;
4017
0
  }
4018
0
4019
0
  if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::tr)) {
4020
0
    aTagName.AssignLiteral("tr");
4021
0
    *aSelectedCount = 1;
4022
0
    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4023
0
    return NS_OK;
4024
0
  }
4025
0
4026
0
  MOZ_ASSERT_UNREACHABLE("Which element was returned?");
4027
0
  return NS_ERROR_UNEXPECTED;
4028
0
}
4029
4030
already_AddRefed<Element>
4031
HTMLEditor::GetSelectedOrParentTableElement(
4032
              Selection& aSelection,
4033
              ErrorResult& aRv,
4034
              bool* aIsCellSelected /* = nullptr */) const
4035
0
{
4036
0
  MOZ_ASSERT(!aRv.Failed());
4037
0
4038
0
  if (aIsCellSelected) {
4039
0
    *aIsCellSelected = false;
4040
0
  }
4041
0
4042
0
  // Try to get the first selected cell, first.
4043
0
  RefPtr<Element> cellElement =
4044
0
    GetFirstSelectedTableCellElement(aSelection, aRv);
4045
0
  if (NS_WARN_IF(aRv.Failed())) {
4046
0
    return nullptr;
4047
0
  }
4048
0
4049
0
  if (cellElement) {
4050
0
    if (aIsCellSelected) {
4051
0
      *aIsCellSelected = true;
4052
0
    }
4053
0
    return cellElement.forget();
4054
0
  }
4055
0
4056
0
  const RangeBoundary& anchorRef = aSelection.AnchorRef();
4057
0
  if (NS_WARN_IF(!anchorRef.IsSet())) {
4058
0
    aRv.Throw(NS_ERROR_FAILURE);
4059
0
    return nullptr;
4060
0
  }
4061
0
4062
0
  // If anchor selects a <td>, <table> or <tr>, return it.
4063
0
  if (anchorRef.Container()->HasChildNodes()) {
4064
0
    nsIContent* selectedContent = anchorRef.GetChildAtOffset();
4065
0
    if (selectedContent) {
4066
0
      // XXX Why do we ignore <th> element in this case?
4067
0
      if (selectedContent->IsHTMLElement(nsGkAtoms::td)) {
4068
0
        // FYI: If first range selects a <tr> element, but the other selects
4069
0
        //      a <td> element, you can reach here.
4070
0
        // Each cell is in its own selection range in this case.
4071
0
        // XXX Although, other ranges may not select cells, though.
4072
0
        if (aIsCellSelected) {
4073
0
          *aIsCellSelected = true;
4074
0
        }
4075
0
        return do_AddRef(selectedContent->AsElement());
4076
0
      }
4077
0
      if (selectedContent->IsAnyOfHTMLElements(nsGkAtoms::table,
4078
0
                                               nsGkAtoms::tr)) {
4079
0
        return do_AddRef(selectedContent->AsElement());
4080
0
      }
4081
0
    }
4082
0
  }
4083
0
4084
0
  // Then, look for a cell element (either <td> or <th>) which contains
4085
0
  // the anchor container.
4086
0
  cellElement = GetElementOrParentByTagNameInternal(*nsGkAtoms::td,
4087
0
                                                    *anchorRef.Container());
4088
0
  if (!cellElement) {
4089
0
    return nullptr; // Not in table.
4090
0
  }
4091
0
  // Don't set *aIsCellSelected to true in this case because it does NOT
4092
0
  // select a cell, just in a cell.
4093
0
  return cellElement.forget();
4094
0
}
4095
4096
NS_IMETHODIMP
4097
HTMLEditor::GetSelectedCellsType(Element* aElement,
4098
                                 uint32_t* aSelectionType)
4099
0
{
4100
0
  NS_ENSURE_ARG_POINTER(aSelectionType);
4101
0
  *aSelectionType = 0;
4102
0
4103
0
  RefPtr<Selection> selection = GetSelection();
4104
0
  if (NS_WARN_IF(!selection)) {
4105
0
    return NS_ERROR_FAILURE;
4106
0
  }
4107
0
4108
0
  // Be sure we have a table element
4109
0
  //  (if aElement is null, this uses selection's anchor node)
4110
0
  RefPtr<Element> table;
4111
0
  if (aElement) {
4112
0
    table = GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aElement);
4113
0
    if (NS_WARN_IF(!table)) {
4114
0
      return NS_ERROR_FAILURE;
4115
0
    }
4116
0
  } else {
4117
0
    table =
4118
0
      GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
4119
0
    if (NS_WARN_IF(!table)) {
4120
0
      return NS_ERROR_FAILURE;
4121
0
    }
4122
0
  }
4123
0
4124
0
  ErrorResult error;
4125
0
  TableSize tableSize(*this, *table, error);
4126
0
  if (NS_WARN_IF(error.Failed())) {
4127
0
    return error.StealNSResult();
4128
0
  }
4129
0
4130
0
  // Traverse all selected cells
4131
0
  RefPtr<Element> selectedCell =
4132
0
    GetFirstSelectedTableCellElement(*selection, error);
4133
0
  if (NS_WARN_IF(error.Failed())) {
4134
0
    return error.StealNSResult();
4135
0
  }
4136
0
  if (!selectedCell) {
4137
0
    return NS_OK;
4138
0
  }
4139
0
4140
0
  // We have at least one selected cell, so set return value
4141
0
  *aSelectionType = static_cast<uint32_t>(TableSelection::Cell);
4142
0
4143
0
  // Store indexes of each row/col to avoid duplication of searches
4144
0
  nsTArray<int32_t> indexArray;
4145
0
4146
0
  bool allCellsInRowAreSelected = false;
4147
0
  bool allCellsInColAreSelected = false;
4148
0
  IgnoredErrorResult ignoredError;
4149
0
  while (selectedCell) {
4150
0
    CellIndexes selectedCellIndexes(*selectedCell, error);
4151
0
    if (NS_WARN_IF(error.Failed())) {
4152
0
      return error.StealNSResult();
4153
0
    }
4154
0
    if (!indexArray.Contains(selectedCellIndexes.mColumn)) {
4155
0
      indexArray.AppendElement(selectedCellIndexes.mColumn);
4156
0
      allCellsInRowAreSelected =
4157
0
        AllCellsInRowSelected(table, selectedCellIndexes.mRow,
4158
0
                              tableSize.mColumnCount);
4159
0
      // We're done as soon as we fail for any row
4160
0
      if (!allCellsInRowAreSelected) {
4161
0
        break;
4162
0
      }
4163
0
    }
4164
0
    selectedCell = GetNextSelectedTableCellElement(*selection, ignoredError);
4165
0
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
4166
0
      "Failed to get next selected table cell element");
4167
0
  }
4168
0
4169
0
  if (allCellsInRowAreSelected) {
4170
0
    *aSelectionType = static_cast<uint32_t>(TableSelection::Row);
4171
0
    return NS_OK;
4172
0
  }
4173
0
  // Test for columns
4174
0
4175
0
  // Empty the indexArray
4176
0
  indexArray.Clear();
4177
0
4178
0
  // Start at first cell again
4179
0
  selectedCell = GetFirstSelectedTableCellElement(*selection, ignoredError);
4180
0
  while (selectedCell) {
4181
0
    CellIndexes selectedCellIndexes(*selectedCell, error);
4182
0
    if (NS_WARN_IF(error.Failed())) {
4183
0
      return error.StealNSResult();
4184
0
    }
4185
0
4186
0
    if (!indexArray.Contains(selectedCellIndexes.mRow)) {
4187
0
      indexArray.AppendElement(selectedCellIndexes.mColumn);
4188
0
      allCellsInColAreSelected =
4189
0
        AllCellsInColumnSelected(table, selectedCellIndexes.mColumn,
4190
0
                                 tableSize.mRowCount);
4191
0
      // We're done as soon as we fail for any column
4192
0
      if (!allCellsInRowAreSelected) {
4193
0
        break;
4194
0
      }
4195
0
    }
4196
0
    selectedCell = GetNextSelectedTableCellElement(*selection, ignoredError);
4197
0
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
4198
0
      "Failed to get next selected table cell element");
4199
0
  }
4200
0
  if (allCellsInColAreSelected) {
4201
0
    *aSelectionType = static_cast<uint32_t>(TableSelection::Column);
4202
0
  }
4203
0
4204
0
  return NS_OK;
4205
0
}
4206
4207
bool
4208
HTMLEditor::AllCellsInRowSelected(Element* aTable,
4209
                                  int32_t aRowIndex,
4210
                                  int32_t aNumberOfColumns)
4211
0
{
4212
0
  NS_ENSURE_TRUE(aTable, false);
4213
0
4214
0
  int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
4215
0
  bool    isSelected;
4216
0
4217
0
  for (int32_t col = 0; col < aNumberOfColumns;
4218
0
       col += std::max(actualColSpan, 1)) {
4219
0
    RefPtr<Element> cell;
4220
0
    nsresult rv = GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell),
4221
0
                                &curStartRowIndex, &curStartColIndex,
4222
0
                                &rowSpan, &colSpan,
4223
0
                                &actualRowSpan, &actualColSpan, &isSelected);
4224
0
4225
0
    NS_ENSURE_SUCCESS(rv, false);
4226
0
    // If no cell, we may have a "ragged" right edge,
4227
0
    //   so return TRUE only if we already found a cell in the row
4228
0
    NS_ENSURE_TRUE(cell, (col > 0) ? true : false);
4229
0
4230
0
    // Return as soon as a non-selected cell is found
4231
0
    NS_ENSURE_TRUE(isSelected, false);
4232
0
4233
0
    NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in AllCellsInRowSelected");
4234
0
  }
4235
0
  return true;
4236
0
}
4237
4238
bool
4239
HTMLEditor::AllCellsInColumnSelected(Element* aTable,
4240
                                     int32_t aColIndex,
4241
                                     int32_t aNumberOfRows)
4242
0
{
4243
0
  NS_ENSURE_TRUE(aTable, false);
4244
0
4245
0
  int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
4246
0
  bool    isSelected;
4247
0
4248
0
  for (int32_t row = 0; row < aNumberOfRows;
4249
0
       row += std::max(actualRowSpan, 1)) {
4250
0
    RefPtr<Element> cell;
4251
0
    nsresult rv = GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell),
4252
0
                                &curStartRowIndex, &curStartColIndex,
4253
0
                                &rowSpan, &colSpan,
4254
0
                                &actualRowSpan, &actualColSpan, &isSelected);
4255
0
4256
0
    NS_ENSURE_SUCCESS(rv, false);
4257
0
    // If no cell, we must have a "ragged" right edge on the last column
4258
0
    //   so return TRUE only if we already found a cell in the row
4259
0
    NS_ENSURE_TRUE(cell, (row > 0) ? true : false);
4260
0
4261
0
    // Return as soon as a non-selected cell is found
4262
0
    NS_ENSURE_TRUE(isSelected, false);
4263
0
  }
4264
0
  return true;
4265
0
}
4266
4267
bool
4268
HTMLEditor::IsEmptyCell(dom::Element* aCell)
4269
0
{
4270
0
  MOZ_ASSERT(aCell);
4271
0
4272
0
  // Check if target only contains empty text node or <br>
4273
0
  nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild();
4274
0
  if (!cellChild) {
4275
0
    return false;
4276
0
  }
4277
0
4278
0
  nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling();
4279
0
  if (nextChild) {
4280
0
    return false;
4281
0
  }
4282
0
4283
0
  // We insert a single break into a cell by default
4284
0
  //   to have some place to locate a cursor -- it is dispensable
4285
0
  if (cellChild->IsHTMLElement(nsGkAtoms::br)) {
4286
0
    return true;
4287
0
  }
4288
0
4289
0
  bool isEmpty;
4290
0
  // Or check if no real content
4291
0
  nsresult rv = IsEmptyNode(cellChild, &isEmpty, false, false);
4292
0
  NS_ENSURE_SUCCESS(rv, false);
4293
0
  return isEmpty;
4294
0
}
4295
4296
} // namespace mozilla