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