/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 | ¤tRowIndex, ¤tColIndex, |
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 | ¤tRowIndex, ¤tColIndex, |
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 | ¤tRowIndex, ¤tColIndex, &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 | ¤tRowIndex, ¤tColIndex, &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 |