diff -r 000000000000 -r 6474c204b198 editor/libeditor/html/nsTableEditor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor/libeditor/html/nsTableEditor.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,3438 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#include "mozilla/Assertions.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/Element.h" +#include "nsAString.h" +#include "nsAlgorithm.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsEditProperty.h" +#include "nsEditor.h" +#include "nsEditorUtils.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsHTMLEditUtils.h" +#include "nsHTMLEditor.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIDOMElement.h" +#include "nsIDOMNode.h" +#include "nsIDOMRange.h" +#include "nsIEditor.h" +#include "nsIFrame.h" +#include "nsIHTMLEditor.h" +#include "nsINode.h" +#include "nsIPresShell.h" +#include "nsISupportsUtils.h" +#include "nsITableCellLayout.h" // For efficient access to table cell +#include "nsITableEditor.h" +#include "nsLiteralString.h" +#include "nsQueryFrame.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTableCellFrame.h" +#include "nsTableOuterFrame.h" +#include "nscore.h" +#include + +using namespace mozilla; +using namespace mozilla::dom; + +/*************************************************************************** + * stack based helper class for restoring selection after table edit + */ +class MOZ_STACK_CLASS nsSetSelectionAfterTableEdit +{ + private: + nsCOMPtr mEd; + nsCOMPtr mTable; + int32_t mCol, mRow, mDirection, mSelected; + public: + nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, + int32_t aRow, int32_t aCol, int32_t aDirection, + bool aSelected) : + mEd(do_QueryInterface(aEd)) + { + mTable = aTable; + mRow = aRow; + mCol = aCol; + mDirection = aDirection; + mSelected = aSelected; + } + + ~nsSetSelectionAfterTableEdit() + { + if (mEd) + mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected); + } + // This is needed to abort the caret reset in the destructor + // when one method yields control to another + void CancelSetCaret() {mEd = nullptr; mTable = nullptr;} +}; + +// Stack-class to turn on/off selection batching for table selection +class MOZ_STACK_CLASS nsSelectionBatcherForTable +{ +private: + nsCOMPtr mSelection; +public: + nsSelectionBatcherForTable(nsISelection *aSelection) + { + nsCOMPtr sel(aSelection); + mSelection = do_QueryInterface(sel); + if (mSelection) mSelection->StartBatchChanges(); + } + virtual ~nsSelectionBatcherForTable() + { + if (mSelection) mSelection->EndBatchChanges(); + } +}; + +// Table Editing helper utilities (not exposed in IDL) + +NS_IMETHODIMP +nsHTMLEditor::InsertCell(nsIDOMElement *aCell, int32_t aRowSpan, int32_t aColSpan, + bool aAfter, bool aIsHeader, nsIDOMElement **aNewCell) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + if (aNewCell) *aNewCell = nullptr; + + // And the parent and offsets needed to do an insert + nsCOMPtr cellParent; + nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(cellParent, NS_ERROR_NULL_POINTER); + + int32_t cellOffset = GetChildOffset(aCell, cellParent); + + nsCOMPtr newCell; + if (aIsHeader) + res = CreateElementWithDefaults(NS_LITERAL_STRING("th"), getter_AddRefs(newCell)); + else + res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); + + if(NS_FAILED(res)) return res; + if(!newCell) return NS_ERROR_FAILURE; + + //Optional: return new cell created + if (aNewCell) + { + *aNewCell = newCell.get(); + NS_ADDREF(*aNewCell); + } + + if( aRowSpan > 1) + { + // Note: Do NOT use editor transaction for this + nsAutoString newRowSpan; + newRowSpan.AppendInt(aRowSpan, 10); + newCell->SetAttribute(NS_LITERAL_STRING("rowspan"), newRowSpan); + } + if( aColSpan > 1) + { + // Note: Do NOT use editor transaction for this + nsAutoString newColSpan; + newColSpan.AppendInt(aColSpan, 10); + newCell->SetAttribute(NS_LITERAL_STRING("colspan"), newColSpan); + } + if(aAfter) cellOffset++; + + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + return InsertNode(newCell, cellParent, cellOffset); +} + +NS_IMETHODIMP nsHTMLEditor::SetColSpan(nsIDOMElement *aCell, int32_t aColSpan) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + nsAutoString newSpan; + newSpan.AppendInt(aColSpan, 10); + return SetAttribute(aCell, NS_LITERAL_STRING("colspan"), newSpan); +} + +NS_IMETHODIMP nsHTMLEditor::SetRowSpan(nsIDOMElement *aCell, int32_t aRowSpan) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + nsAutoString newSpan; + newSpan.AppendInt(aRowSpan, 10); + return SetAttribute(aCell, NS_LITERAL_STRING("rowspan"), newSpan); +} + +/****************************************************************/ + +// Table Editing interface methods + +NS_IMETHODIMP +nsHTMLEditor::InsertTableCell(int32_t aNumber, bool aAfter) +{ + nsCOMPtr table; + nsCOMPtr curCell; + nsCOMPtr cellParent; + int32_t cellOffset, startRowIndex, startColIndex; + nsresult res = GetCellContext(nullptr, + getter_AddRefs(table), + getter_AddRefs(curCell), + getter_AddRefs(cellParent), &cellOffset, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); + + // Get more data for current cell in row we are inserting at (we need COLSPAN) + int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + res = GetCellDataAt(table, startRowIndex, startColIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); + int32_t newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex; + //We control selection resetting after the insert... + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, false); + //...so suppress Rules System selection munging + nsAutoTxnsConserveSelection dontChangeSelection(this); + + int32_t i; + for (i = 0; i < aNumber; i++) + { + nsCOMPtr newCell; + res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); + if (NS_SUCCEEDED(res) && newCell) + { + if (aAfter) cellOffset++; + res = InsertNode(newCell, cellParent, cellOffset); + if(NS_FAILED(res)) break; + } + } + return res; +} + + +NS_IMETHODIMP +nsHTMLEditor::GetFirstRow(nsIDOMElement* aTableElement, nsIDOMNode** aRowNode) +{ + NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); + + *aRowNode = nullptr; + + NS_ENSURE_TRUE(aTableElement, NS_ERROR_NULL_POINTER); + + nsCOMPtr tableElement; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTableElement, getter_AddRefs(tableElement)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(tableElement, NS_ERROR_NULL_POINTER); + + nsCOMPtr tableChild; + res = tableElement->GetFirstChild(getter_AddRefs(tableChild)); + NS_ENSURE_SUCCESS(res, res); + + while (tableChild) + { + nsCOMPtr content = do_QueryInterface(tableChild); + if (content) + { + nsIAtom *atom = content->Tag(); + + if (atom == nsEditProperty::tr) + { + // Found a row directly under + *aRowNode = tableChild; + NS_ADDREF(*aRowNode); + return NS_OK; + } + // Look for row in one of the row container elements + if (atom == nsEditProperty::tbody || + atom == nsEditProperty::thead || + atom == nsEditProperty::tfoot) + { + nsCOMPtr rowNode; + res = tableChild->GetFirstChild(getter_AddRefs(rowNode)); + NS_ENSURE_SUCCESS(res, res); + + // We can encounter textnodes here -- must find a row + while (rowNode && !nsHTMLEditUtils::IsTableRow(rowNode)) + { + nsCOMPtr nextNode; + res = rowNode->GetNextSibling(getter_AddRefs(nextNode)); + NS_ENSURE_SUCCESS(res, res); + + rowNode = nextNode; + } + if(rowNode) + { + *aRowNode = rowNode.get(); + NS_ADDREF(*aRowNode); + return NS_OK; + } + } + } + // Here if table child was a CAPTION or COLGROUP + // or child of a row parent wasn't a row (bad HTML?), + // or first child was a textnode + // Look in next table child + nsCOMPtr nextChild; + res = tableChild->GetNextSibling(getter_AddRefs(nextChild)); + NS_ENSURE_SUCCESS(res, res); + + tableChild = nextChild; + }; + // If here, row was not found + return NS_EDITOR_ELEMENT_NOT_FOUND; +} + +NS_IMETHODIMP +nsHTMLEditor::GetNextRow(nsIDOMNode* aCurrentRowNode, nsIDOMNode **aRowNode) +{ + NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); + + *aRowNode = nullptr; + + NS_ENSURE_TRUE(aCurrentRowNode, NS_ERROR_NULL_POINTER); + + if (!nsHTMLEditUtils::IsTableRow(aCurrentRowNode)) + return NS_ERROR_FAILURE; + + nsCOMPtr nextRow; + nsresult res = aCurrentRowNode->GetNextSibling(getter_AddRefs(nextRow)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr nextNode; + + // Skip over any textnodes here + while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow)) + { + res = nextRow->GetNextSibling(getter_AddRefs(nextNode)); + NS_ENSURE_SUCCESS(res, res); + + nextRow = nextNode; + } + if(nextRow) + { + *aRowNode = nextRow.get(); + NS_ADDREF(*aRowNode); + return NS_OK; + } + + // No row found, search for rows in other table sections + nsCOMPtr rowParent; + res = aCurrentRowNode->GetParentNode(getter_AddRefs(rowParent)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(rowParent, NS_ERROR_NULL_POINTER); + + nsCOMPtr parentSibling; + res = rowParent->GetNextSibling(getter_AddRefs(parentSibling)); + NS_ENSURE_SUCCESS(res, res); + + while (parentSibling) + { + res = parentSibling->GetFirstChild(getter_AddRefs(nextRow)); + NS_ENSURE_SUCCESS(res, res); + + // We can encounter textnodes here -- must find a row + while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow)) + { + res = nextRow->GetNextSibling(getter_AddRefs(nextNode)); + NS_ENSURE_SUCCESS(res, res); + + nextRow = nextNode; + } + if(nextRow) + { + *aRowNode = nextRow.get(); + NS_ADDREF(*aRowNode); + return NS_OK; + } +#ifdef DEBUG_cmanske + printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n"); +#endif + // We arrive here only if a table section has no children + // or first child of section is not a row (bad HTML or more "_moz_text" nodes!) + // So look for another section sibling + res = parentSibling->GetNextSibling(getter_AddRefs(nextNode)); + NS_ENSURE_SUCCESS(res, res); + + parentSibling = nextNode; + } + // If here, row was not found + return NS_EDITOR_ELEMENT_NOT_FOUND; +} + +NS_IMETHODIMP +nsHTMLEditor::GetLastCellInRow(nsIDOMNode* aRowNode, nsIDOMNode** aCellNode) +{ + NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER); + + *aCellNode = nullptr; + + NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); + + nsCOMPtr rowChild; + nsresult res = aRowNode->GetLastChild(getter_AddRefs(rowChild)); + NS_ENSURE_SUCCESS(res, res); + + while (rowChild && !nsHTMLEditUtils::IsTableCell(rowChild)) + { + // Skip over textnodes + nsCOMPtr previousChild; + res = rowChild->GetPreviousSibling(getter_AddRefs(previousChild)); + NS_ENSURE_SUCCESS(res, res); + + rowChild = previousChild; + }; + if (rowChild) + { + *aCellNode = rowChild.get(); + NS_ADDREF(*aCellNode); + return NS_OK; + } + // If here, cell was not found + return NS_EDITOR_ELEMENT_NOT_FOUND; +} + +NS_IMETHODIMP +nsHTMLEditor::InsertTableColumn(int32_t aNumber, bool aAfter) +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr curCell; + int32_t startRowIndex, startColIndex; + nsresult res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(curCell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); + + // Get more data for current cell (we need ROWSPAN) + int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + res = GetCellDataAt(table, startRowIndex, startColIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); + + nsAutoEditBatch beginBatching(this); + // Prevent auto insertion of BR in new cell until we're done + nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); + + // Use column after current cell if requested + if (aAfter) + { + startColIndex += actualColSpan; + //Detect when user is adding after a COLSPAN=0 case + // Assume they want to stop the "0" behavior and + // really add a new column. Thus we set the + // colspan to its true value + if (colSpan == 0) + SetColSpan(curCell, actualColSpan); + } + + int32_t rowCount, colCount, rowIndex; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + //We reset caret in destructor... + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); + //.. so suppress Rules System selection munging + nsAutoTxnsConserveSelection dontChangeSelection(this); + + // If we are inserting after all existing columns + // Make sure table is "well formed" + // before appending new column + if (startColIndex >= colCount) + NormalizeTable(table); + + nsCOMPtr rowNode; + for ( rowIndex = 0; rowIndex < rowCount; rowIndex++) + { +#ifdef DEBUG_cmanske + if (rowIndex == rowCount-1) + printf(" ***InsertTableColumn: Inserting cell at last row: %d\n", rowIndex); +#endif + + if (startColIndex < colCount) + { + // We are inserting before an existing column + res = GetCellDataAt(table, rowIndex, startColIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + + // Don't fail entire process if we fail to find a cell + // (may fail just in particular rows with < adequate cells per row) + if (curCell) + { + if (curStartColIndex < startColIndex) + { + // We have a cell spanning this location + // Simply increase its colspan to keep table rectangular + // Note: we do nothing if colsSpan=0, + // since it should automatically span the new column + if (colSpan > 0) + SetColSpan(curCell, colSpan+aNumber); + } else { + // Simply set selection to the current cell + // so we can let InsertTableCell() do the work + // Insert a new cell before current one + selection->Collapse(curCell, 0); + res = InsertTableCell(aNumber, false); + } + } + } else { + // Get current row and append new cells after last cell in row + if(rowIndex == 0) + res = GetFirstRow(table.get(), getter_AddRefs(rowNode)); + else + { + nsCOMPtr nextRow; + res = GetNextRow(rowNode.get(), getter_AddRefs(nextRow)); + rowNode = nextRow; + } + NS_ENSURE_SUCCESS(res, res); + + if (rowNode) + { + nsCOMPtr lastCell; + res = GetLastCellInRow(rowNode, getter_AddRefs(lastCell)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(lastCell, NS_ERROR_FAILURE); + + curCell = do_QueryInterface(lastCell); + if (curCell) + { + // Simply add same number of cells to each row + // Although tempted to check cell indexes for curCell, + // the effects of COLSPAN>1 in some cells makes this futile! + // We must use NormalizeTable first to assure + // that there are cells in each cellmap location + selection->Collapse(curCell, 0); + res = InsertTableCell(aNumber, true); + } + } + } + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::InsertTableRow(int32_t aNumber, bool aAfter) +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr curCell; + + int32_t startRowIndex, startColIndex; + nsresult res = GetCellContext(nullptr, + getter_AddRefs(table), + getter_AddRefs(curCell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); + + // Get more data for current cell in row we are inserting at (we need COLSPAN) + int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + res = GetCellDataAt(table, startRowIndex, startColIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + nsAutoEditBatch beginBatching(this); + // Prevent auto insertion of BR in new cell until we're done + nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); + + if (aAfter) + { + // Use row after current cell + startRowIndex += actualRowSpan; + + //Detect when user is adding after a ROWSPAN=0 case + // Assume they want to stop the "0" behavior and + // really add a new row. Thus we set the + // rowspan to its true value + if (rowSpan == 0) + SetRowSpan(curCell, actualRowSpan); + } + + //We control selection resetting after the insert... + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); + //...so suppress Rules System selection munging + nsAutoTxnsConserveSelection dontChangeSelection(this); + + nsCOMPtr cellForRowParent; + int32_t cellsInRow = 0; + if (startRowIndex < rowCount) + { + // We are inserting above an existing row + // Get each cell in the insert row to adjust for COLSPAN effects while we + // count how many cells are needed + int32_t colIndex = 0; + // This returns NS_TABLELAYOUT_CELL_NOT_FOUND when we run past end of row, + // which passes the NS_SUCCEEDED macro + while ( NS_OK == GetCellDataAt(table, startRowIndex, colIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, + &isSelected) ) + { + if (curCell) + { + if (curStartRowIndex < startRowIndex) + { + // We have a cell spanning this location + // Simply increase its rowspan + //Note that if rowSpan == 0, we do nothing, + // since that cell should automatically extend into the new row + if (rowSpan > 0) + SetRowSpan(curCell, rowSpan+aNumber); + } else { + // We have a cell in the insert row + + // Count the number of cells we need to add to the new row + cellsInRow += actualColSpan; + + // Save cell we will use below + if (!cellForRowParent) + cellForRowParent = curCell; + } + // Next cell in row + colIndex += actualColSpan; + } + else + colIndex++; + } + } else { + // We are adding a new row after all others + // If it weren't for colspan=0 effect, + // we could simply use colCount for number of new cells... + cellsInRow = colCount; + + // ...but we must compensate for all cells with rowSpan = 0 in the last row + int32_t lastRow = rowCount-1; + int32_t tempColIndex = 0; + while ( NS_OK == GetCellDataAt(table, lastRow, tempColIndex, + getter_AddRefs(curCell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, + &isSelected) ) + { + if (rowSpan == 0) + cellsInRow -= actualColSpan; + + tempColIndex += actualColSpan; + + // Save cell from the last row that we will use below + if (!cellForRowParent && curStartRowIndex == lastRow) + cellForRowParent = curCell; + } + } + + if (cellsInRow > 0) + { + // The row parent and offset where we will insert new row + nsCOMPtr parentOfRow; + int32_t newRowOffset; + + NS_NAMED_LITERAL_STRING(trStr, "tr"); + if (cellForRowParent) + { + nsCOMPtr parentRow; + res = GetElementOrParentByTagName(trStr, cellForRowParent, getter_AddRefs(parentRow)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER); + + parentRow->GetParentNode(getter_AddRefs(parentOfRow)); + NS_ENSURE_TRUE(parentOfRow, NS_ERROR_NULL_POINTER); + + newRowOffset = GetChildOffset(parentRow, parentOfRow); + + // Adjust for when adding past the end + if (aAfter && startRowIndex >= rowCount) + newRowOffset++; + } + else + return NS_ERROR_FAILURE; + + for (int32_t row = 0; row < aNumber; row++) + { + // Create a new row + nsCOMPtr newRow; + res = CreateElementWithDefaults(trStr, getter_AddRefs(newRow)); + if (NS_SUCCEEDED(res)) + { + NS_ENSURE_TRUE(newRow, NS_ERROR_FAILURE); + + for (int32_t i = 0; i < cellsInRow; i++) + { + nsCOMPtr newCell; + res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(newCell, NS_ERROR_FAILURE); + + // Don't use transaction system yet! (not until entire row is inserted) + nsCOMPtrresultNode; + res = newRow->AppendChild(newCell, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS(res, res); + } + // Use transaction system to insert the entire row+cells + // (Note that rows are inserted at same childoffset each time) + res = InsertNode(newRow, parentOfRow, newRowOffset); + NS_ENSURE_SUCCESS(res, res); + } + } + } + return res; +} + +// Editor helper only +// XXX Code changed for bug 217717 and now we don't need aSelection param +// TODO: Remove aSelection param +NS_IMETHODIMP +nsHTMLEditor::DeleteTable2(nsIDOMElement *aTable, nsISelection *aSelection) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + + // Select the table + nsresult res = ClearSelection(); + if (NS_SUCCEEDED(res)) + res = AppendNodeToSelectionAsRange(aTable); + NS_ENSURE_SUCCESS(res, res); + + return DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip); +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteTable() +{ + nsCOMPtr selection; + nsCOMPtr table; + nsresult res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + nullptr, nullptr, nullptr, nullptr, nullptr); + + NS_ENSURE_SUCCESS(res, res); + + nsAutoEditBatch beginBatching(this); + return DeleteTable2(table, selection); +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteTableCell(int32_t aNumber) +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cell; + int32_t startRowIndex, startColIndex; + + + nsresult res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + + NS_ENSURE_SUCCESS(res, res); + // Don't fail if we didn't find a table or cell + NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + nsAutoEditBatch beginBatching(this); + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + nsCOMPtr firstCell; + nsCOMPtr range; + res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); + NS_ENSURE_SUCCESS(res, res); + + int32_t rangeCount; + res = selection->GetRangeCount(&rangeCount); + NS_ENSURE_SUCCESS(res, res); + + if (firstCell && rangeCount > 1) + { + // When > 1 selected cell, + // ignore aNumber and use selected cells + cell = firstCell; + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Get indexes -- may be different than original cell + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + + // The setCaret object will call SetSelectionAfterTableEdit in its destructor + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); + nsAutoTxnsConserveSelection dontChangeSelection(this); + + bool checkToDeleteRow = true; + bool checkToDeleteColumn = true; + while (cell) + { + bool deleteRow = false; + bool deleteCol = false; + + if (checkToDeleteRow) + { + // Optimize to delete an entire row + // Clear so we don't repeat AllCellsInRowSelected within the same row + checkToDeleteRow = false; + + deleteRow = AllCellsInRowSelected(table, startRowIndex, colCount); + if (deleteRow) + { + // First, find the next cell in a different row + // to continue after we delete this row + int32_t nextRow = startRowIndex; + while (nextRow == startRowIndex) + { + res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if (!cell) break; + res = GetCellIndexes(cell, &nextRow, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + // Delete entire row + res = DeleteRow(table, startRowIndex); + NS_ENSURE_SUCCESS(res, res); + + if (cell) + { + // For the next cell: Subtract 1 for row we deleted + startRowIndex = nextRow - 1; + // Set true since we know we will look at a new row next + checkToDeleteRow = true; + } + } + } + if (!deleteRow) + { + if (checkToDeleteColumn) + { + // Optimize to delete an entire column + // Clear this so we don't repeat AllCellsInColSelected within the same Col + checkToDeleteColumn = false; + + deleteCol = AllCellsInColumnSelected(table, startColIndex, colCount); + if (deleteCol) + { + // First, find the next cell in a different column + // to continue after we delete this column + int32_t nextCol = startColIndex; + while (nextCol == startColIndex) + { + res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if (!cell) break; + res = GetCellIndexes(cell, &startRowIndex, &nextCol); + NS_ENSURE_SUCCESS(res, res); + } + // Delete entire Col + res = DeleteColumn(table, startColIndex); + NS_ENSURE_SUCCESS(res, res); + if (cell) + { + // For the next cell, subtract 1 for col. deleted + startColIndex = nextCol - 1; + // Set true since we know we will look at a new column next + checkToDeleteColumn = true; + } + } + } + if (!deleteCol) + { + // First get the next cell to delete + nsCOMPtr nextCell; + res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(nextCell)); + NS_ENSURE_SUCCESS(res, res); + + // Then delete the cell + res = DeleteNode(cell); + NS_ENSURE_SUCCESS(res, res); + + // The next cell to delete + cell = nextCell; + if (cell) + { + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + } + } + } + } + else for (int32_t i = 0; i < aNumber; i++) + { + res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + if (1 == GetNumberOfCellsInRow(table, startRowIndex)) + { + nsCOMPtr parentRow; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER); + + // We should delete the row instead, + // but first check if its the only row left + // so we can delete the entire table + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + if (rowCount == 1) + return DeleteTable2(table, selection); + + // We need to call DeleteTableRow to handle cells with rowspan + res = DeleteTableRow(1); + NS_ENSURE_SUCCESS(res, res); + } + else + { + // More than 1 cell in the row + + // The setCaret object will call SetSelectionAfterTableEdit in its destructor + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); + nsAutoTxnsConserveSelection dontChangeSelection(this); + + res = DeleteNode(cell); + // If we fail, don't try to delete any more cells??? + NS_ENSURE_SUCCESS(res, res); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteTableCellContents() +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cell; + int32_t startRowIndex, startColIndex; + nsresult res; + res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + + nsAutoEditBatch beginBatching(this); + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + + + nsCOMPtr firstCell; + nsCOMPtr range; + res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); + NS_ENSURE_SUCCESS(res, res); + + + if (firstCell) + { + cell = firstCell; + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); + + while (cell) + { + DeleteCellContents(cell); + if (firstCell) + { + // We doing a selected cells, so do all of them + res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + } + else + cell = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteCellContents(nsIDOMElement *aCell) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + nsCOMPtr child; + bool hasChild; + aCell->HasChildNodes(&hasChild); + + while (hasChild) + { + aCell->GetLastChild(getter_AddRefs(child)); + nsresult res = DeleteNode(child); + NS_ENSURE_SUCCESS(res, res); + aCell->HasChildNodes(&hasChild); + } + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteTableColumn(int32_t aNumber) +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, rowCount, colCount; + nsresult res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Shortcut the case of deleting all columns in table + if(startColIndex == 0 && aNumber >= colCount) + return DeleteTable2(table, selection); + + // Check for counts too high + aNumber = std::min(aNumber,(colCount-startColIndex)); + + nsAutoEditBatch beginBatching(this); + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + // Test if deletion is controlled by selected cells + nsCOMPtr firstCell; + nsCOMPtr range; + res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); + NS_ENSURE_SUCCESS(res, res); + + int32_t rangeCount; + res = selection->GetRangeCount(&rangeCount); + NS_ENSURE_SUCCESS(res, res); + + if (firstCell && rangeCount > 1) + { + // Fetch indexes again - may be different for selected cells + res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + //We control selection resetting after the insert... + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); + + if (firstCell && rangeCount > 1) + { + // Use selected cells to determine what rows to delete + cell = firstCell; + + while (cell) + { + if (cell != firstCell) + { + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + // Find the next cell in a different column + // to continue after we delete this column + int32_t nextCol = startColIndex; + while (nextCol == startColIndex) + { + res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if (!cell) break; + res = GetCellIndexes(cell, &startRowIndex, &nextCol); + NS_ENSURE_SUCCESS(res, res); + } + res = DeleteColumn(table, startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + } + else for (int32_t i = 0; i < aNumber; i++) + { + res = DeleteColumn(table, startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteColumn(nsIDOMElement *aTable, int32_t aColIndex) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + + nsCOMPtr cell; + nsCOMPtr cellInDeleteCol; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + int32_t rowIndex = 0; + nsresult res = NS_OK; + + do { + res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + + NS_ENSURE_SUCCESS(res, res); + + if (cell) + { + // Find cells that don't start in column we are deleting + if (startColIndex < aColIndex || colSpan > 1 || colSpan == 0) + { + // We have a cell spanning this location + // Decrease its colspan to keep table rectangular, + // but if colSpan=0, it will adjust automatically + if (colSpan > 0) + { + NS_ASSERTION((colSpan > 1),"Bad COLSPAN in DeleteTableColumn"); + SetColSpan(cell, colSpan-1); + } + if (startColIndex == aColIndex) + { + // Cell is in column to be deleted, but must have colspan > 1, + // so delete contents of cell instead of cell itself + // (We must have reset colspan above) + DeleteCellContents(cell); + } + // To next cell in column + rowIndex += actualRowSpan; + } + else + { + // Delete the cell + if (1 == GetNumberOfCellsInRow(aTable, rowIndex)) + { + // Only 1 cell in row - delete the row + nsCOMPtr parentRow; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow)); + NS_ENSURE_SUCCESS(res, res); + if(!parentRow) return NS_ERROR_NULL_POINTER; + + // But first check if its the only row left + // so we can delete the entire table + // (This should never happen but it's the safe thing to do) + int32_t rowCount, colCount; + res = GetTableSize(aTable, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + if (rowCount == 1) + { + nsCOMPtr selection; + res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + return DeleteTable2(aTable, selection); + } + + // Delete the row by placing caret in cell we were to delete + // We need to call DeleteTableRow to handle cells with rowspan + res = DeleteRow(aTable, startRowIndex); + NS_ENSURE_SUCCESS(res, res); + + // Note that we don't incremenet rowIndex + // since a row was deleted and "next" + // row now has current rowIndex + } + else + { + // A more "normal" deletion + res = DeleteNode(cell); + NS_ENSURE_SUCCESS(res, res); + + //Skip over any rows spanned by this cell + rowIndex += actualRowSpan; + } + } + } + } while (cell); + + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::DeleteTableRow(int32_t aNumber) +{ + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cell; + int32_t startRowIndex, startColIndex; + int32_t rowCount, colCount; + nsresult res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if no cell found + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Shortcut the case of deleting all rows in table + if(startRowIndex == 0 && aNumber >= rowCount) + return DeleteTable2(table, selection); + + nsAutoEditBatch beginBatching(this); + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + nsCOMPtr firstCell; + nsCOMPtr range; + res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); + NS_ENSURE_SUCCESS(res, res); + + int32_t rangeCount; + res = selection->GetRangeCount(&rangeCount); + NS_ENSURE_SUCCESS(res, res); + + if (firstCell && rangeCount > 1) + { + // Fetch indexes again - may be different for selected cells + res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + + //We control selection resetting after the insert... + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); + // Don't change selection during deletions + nsAutoTxnsConserveSelection dontChangeSelection(this); + + if (firstCell && rangeCount > 1) + { + // Use selected cells to determine what rows to delete + cell = firstCell; + + while (cell) + { + if (cell != firstCell) + { + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + // Find the next cell in a different row + // to continue after we delete this row + int32_t nextRow = startRowIndex; + while (nextRow == startRowIndex) + { + res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if (!cell) break; + res = GetCellIndexes(cell, &nextRow, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + } + // Delete entire row + res = DeleteRow(table, startRowIndex); + NS_ENSURE_SUCCESS(res, res); + } + } + else + { + // Check for counts too high + aNumber = std::min(aNumber,(rowCount-startRowIndex)); + + for (int32_t i = 0; i < aNumber; i++) + { + res = DeleteRow(table, startRowIndex); + // If failed in current row, try the next + if (NS_FAILED(res)) + startRowIndex++; + + // Check if there's a cell in the "next" row + res = GetCellAt(table, startRowIndex, startColIndex, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if(!cell) + break; + } + } + return NS_OK; +} + +// Helper that doesn't batch or change the selection +NS_IMETHODIMP +nsHTMLEditor::DeleteRow(nsIDOMElement *aTable, int32_t aRowIndex) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + + nsCOMPtr cell; + nsCOMPtr cellInDeleteRow; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + int32_t colIndex = 0; + nsresult res = NS_OK; + + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + // The list of cells we will change rowspan in + // and the new rowspan values for each + nsTArray > spanCellList; + nsTArray newSpanList; + + int32_t rowCount, colCount; + res = GetTableSize(aTable, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Scan through cells in row to do rowspan adjustments + // Note that after we delete row, startRowIndex will point to the + // cells in the next row to be deleted + do { + if (aRowIndex >= rowCount || colIndex >= colCount) + break; + + res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + + // We don't fail if we don't find a cell, so this must be real bad + if(NS_FAILED(res)) return res; + + // Compensate for cells that don't start or extend below the row we are deleting + if (cell) + { + if (startRowIndex < aRowIndex) + { + // Cell starts in row above us + // Decrease its rowspan to keep table rectangular + // but we don't need to do this if rowspan=0, + // since it will automatically adjust + if (rowSpan > 0) + { + // Build list of cells to change rowspan + // We can't do it now since it upsets cell map, + // so we will do it after deleting the row + spanCellList.AppendElement(cell); + newSpanList.AppendElement(std::max((aRowIndex - startRowIndex), actualRowSpan-1)); + } + } + else + { + if (rowSpan > 1) + { + //Cell spans below row to delete, + // so we must insert new cells to keep rows below even + // Note that we test "rowSpan" so we don't do this if rowSpan = 0 (automatic readjustment) + res = SplitCellIntoRows(aTable, startRowIndex, startColIndex, + aRowIndex - startRowIndex + 1, // The row above the row to insert new cell into + actualRowSpan - 1, nullptr); // Span remaining below + NS_ENSURE_SUCCESS(res, res); + } + if (!cellInDeleteRow) + cellInDeleteRow = cell; // Reference cell to find row to delete + } + // Skip over other columns spanned by this cell + colIndex += actualColSpan; + } + } while (cell); + + // Things are messed up if we didn't find a cell in the row! + NS_ENSURE_TRUE(cellInDeleteRow, NS_ERROR_FAILURE); + + // Delete the entire row + nsCOMPtr parentRow; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cellInDeleteRow, getter_AddRefs(parentRow)); + NS_ENSURE_SUCCESS(res, res); + + if (parentRow) + { + res = DeleteNode(parentRow); + NS_ENSURE_SUCCESS(res, res); + } + + // Now we can set new rowspans for cells stored above + for (uint32_t i = 0, n = spanCellList.Length(); i < n; i++) + { + nsIDOMElement *cellPtr = spanCellList[i]; + if (cellPtr) + { + res = SetRowSpan(cellPtr, newSpanList[i]); + NS_ENSURE_SUCCESS(res, res); + } + } + return NS_OK; +} + + +NS_IMETHODIMP +nsHTMLEditor::SelectTable() +{ + nsCOMPtr table; + nsresult res = NS_ERROR_FAILURE; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if we didn't find a table + NS_ENSURE_TRUE(table, NS_OK); + + res = ClearSelection(); + if (NS_SUCCEEDED(res)) + res = AppendNodeToSelectionAsRange(table); + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectTableCell() +{ + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + res = ClearSelection(); + if (NS_SUCCEEDED(res)) + res = AppendNodeToSelectionAsRange(cell); + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) +{ + NS_ENSURE_TRUE(aStartCell && aEndCell, NS_ERROR_NULL_POINTER); + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + NS_NAMED_LITERAL_STRING(tableStr, "table"); + nsCOMPtr table; + res = GetElementOrParentByTagName(tableStr, aStartCell, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + + nsCOMPtr endTable; + res = GetElementOrParentByTagName(tableStr, aEndCell, getter_AddRefs(endTable)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(endTable, NS_ERROR_FAILURE); + + // We can only select a block if within the same table, + // so do nothing if not within one table + if (table != endTable) return NS_OK; + + int32_t startRowIndex, startColIndex, endRowIndex, endColIndex; + + // Get starting and ending cells' location in the cellmap + res = GetCellIndexes(aStartCell, &startRowIndex, &startColIndex); + if(NS_FAILED(res)) return res; + + res = GetCellIndexes(aEndCell, &endRowIndex, &endColIndex); + if(NS_FAILED(res)) return res; + + // Suppress nsISelectionListener notification + // until all selection changes are finished + nsSelectionBatcherForTable selectionBatcher(selection); + + // Examine all cell nodes in current selection and + // remove those outside the new block cell region + int32_t minColumn = std::min(startColIndex, endColIndex); + int32_t minRow = std::min(startRowIndex, endRowIndex); + int32_t maxColumn = std::max(startColIndex, endColIndex); + int32_t maxRow = std::max(startRowIndex, endRowIndex); + + nsCOMPtr cell; + int32_t currentRowIndex, currentColIndex; + nsCOMPtr range; + res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK; + + while (cell) + { + res = GetCellIndexes(cell, ¤tRowIndex, ¤tColIndex); + NS_ENSURE_SUCCESS(res, res); + + if (currentRowIndex < maxRow || currentRowIndex > maxRow || + currentColIndex < maxColumn || currentColIndex > maxColumn) + { + selection->RemoveRange(range); + // Since we've removed the range, decrement pointer to next range + mSelectedCellIndex--; + } + res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + } + + int32_t rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + for (int32_t row = minRow; row <= maxRow; row++) + { + for(int32_t col = minColumn; col <= maxColumn; col += std::max(actualColSpan, 1)) + { + res = GetCellDataAt(table, row, col, getter_AddRefs(cell), + ¤tRowIndex, ¤tColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if (NS_FAILED(res)) break; + // Skip cells that already selected or are spanned from previous locations + if (!isSelected && cell && row == currentRowIndex && col == currentColIndex) + { + res = AppendNodeToSelectionAsRange(cell); + if (NS_FAILED(res)) break; + } + } + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectAllTableCells() +{ + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + + // Don't fail if we didn't find a cell + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + nsCOMPtr startCell = cell; + + // Get parent table + nsCOMPtr table; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + if(!table) return NS_ERROR_NULL_POINTER; + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr selection; + res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + // Suppress nsISelectionListener notification + // until all selection changes are finished + nsSelectionBatcherForTable selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + bool cellSelected = false; + int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + bool isSelected; + for(int32_t row = 0; row < rowCount; row++) + { + for(int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) + { + res = GetCellDataAt(table, row, col, getter_AddRefs(cell), + ¤tRowIndex, ¤tColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && row == currentRowIndex && col == currentColIndex) + { + res = AppendNodeToSelectionAsRange(cell); + if (NS_FAILED(res)) break; + cellSelected = true; + } + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + return AppendNodeToSelectionAsRange(startCell); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectTableRow() +{ + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + + // Don't fail if we didn't find a cell + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + nsCOMPtr startCell = cell; + + // Get table and location of cell: + nsCOMPtr selection; + nsCOMPtr table; + int32_t startRowIndex, startColIndex; + + res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + //Note: At this point, we could get first and last cells in row, + // then call SelectBlockOfCells, but that would take just + // a little less code, so the following is more efficient + + // Suppress nsISelectionListener notification + // until all selection changes are finished + nsSelectionBatcherForTable selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same row as current cell + bool cellSelected = false; + int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + bool isSelected; + for(int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) + { + res = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell), + ¤tRowIndex, ¤tColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == startRowIndex && currentColIndex == col) + { + res = AppendNodeToSelectionAsRange(cell); + if (NS_FAILED(res)) break; + cellSelected = true; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + return AppendNodeToSelectionAsRange(startCell); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectTableColumn() +{ + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + + // Don't fail if we didn't find a cell + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + nsCOMPtr startCell = cell; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + int32_t startRowIndex, startColIndex; + + res = GetCellContext(getter_AddRefs(selection), + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Suppress nsISelectionListener notification + // until all selection changes are finished + nsSelectionBatcherForTable selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + bool cellSelected = false; + int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + bool isSelected; + for(int32_t row = 0; row < rowCount; row += std::max(actualRowSpan, 1)) + { + res = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell), + ¤tRowIndex, ¤tColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == row && currentColIndex == startColIndex) + { + res = AppendNodeToSelectionAsRange(cell); + if (NS_FAILED(res)) break; + cellSelected = true; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + return AppendNodeToSelectionAsRange(startCell); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SplitTableCell() +{ + nsCOMPtr table; + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan; + // Get cell, table, etc. at selection anchor node + nsresult res = GetCellContext(nullptr, + getter_AddRefs(table), + getter_AddRefs(cell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + if(!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + // We need rowspan and colspan data + res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan); + NS_ENSURE_SUCCESS(res, res); + + // Must have some span to split + if (actualRowSpan <= 1 && actualColSpan <= 1) + return NS_OK; + + nsAutoEditBatch beginBatching(this); + // Prevent auto insertion of BR in new cell until we're done + nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); + + // We reset selection + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); + //...so suppress Rules System selection munging + nsAutoTxnsConserveSelection dontChangeSelection(this); + + nsCOMPtr newCell; + int32_t rowIndex = startRowIndex; + int32_t rowSpanBelow, colSpanAfter; + + // Split up cell row-wise first into rowspan=1 above, and the rest below, + // whittling away at the cell below until no more extra span + for (rowSpanBelow = actualRowSpan-1; rowSpanBelow >= 0; rowSpanBelow--) + { + // We really split row-wise only if we had rowspan > 1 + if (rowSpanBelow > 0) + { + res = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow, getter_AddRefs(newCell)); + NS_ENSURE_SUCCESS(res, res); + CopyCellBackgroundColor(newCell, cell); + } + int32_t colIndex = startColIndex; + // Now split the cell with rowspan = 1 into cells if it has colSpan > 1 + for (colSpanAfter = actualColSpan-1; colSpanAfter > 0; colSpanAfter--) + { + res = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter, getter_AddRefs(newCell)); + NS_ENSURE_SUCCESS(res, res); + CopyCellBackgroundColor(newCell, cell); + colIndex++; + } + // Point to the new cell and repeat + rowIndex++; + } + return res; +} + +nsresult +nsHTMLEditor::CopyCellBackgroundColor(nsIDOMElement *destCell, nsIDOMElement *sourceCell) +{ + NS_ENSURE_TRUE(destCell && sourceCell, NS_ERROR_NULL_POINTER); + + // Copy backgournd color to new cell + NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor"); + nsAutoString color; + bool isSet; + nsresult res = GetAttributeValue(sourceCell, bgcolor, color, &isSet); + + if (NS_SUCCEEDED(res) && isSet) + res = SetAttribute(destCell, bgcolor, color); + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t aColSpanLeft, int32_t aColSpanRight, + nsIDOMElement **aNewCell) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + if (aNewCell) *aNewCell = nullptr; + + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER); + + // We can't split! + if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) + return NS_OK; + + // Reduce colspan of cell to split + res = SetColSpan(cell, aColSpanLeft); + NS_ENSURE_SUCCESS(res, res); + + // Insert new cell after using the remaining span + // and always get the new cell so we can copy the background color; + nsCOMPtr newCell; + res = InsertCell(cell, actualRowSpan, aColSpanRight, true, false, getter_AddRefs(newCell)); + NS_ENSURE_SUCCESS(res, res); + if (newCell) + { + if (aNewCell) + { + *aNewCell = newCell.get(); + NS_ADDREF(*aNewCell); + } + res = CopyCellBackgroundColor(newCell, cell); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t aRowSpanAbove, int32_t aRowSpanBelow, + nsIDOMElement **aNewCell) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + if (aNewCell) *aNewCell = nullptr; + + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER); + + // We can't split! + if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) + return NS_OK; + + int32_t rowCount, colCount; + res = GetTableSize(aTable, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr cell2; + nsCOMPtr lastCellFound; + int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; + bool isSelected2; + int32_t colIndex = 0; + bool insertAfter = (startColIndex > 0); + // This is the row we will insert new cell into + int32_t rowBelowIndex = startRowIndex+aRowSpanAbove; + + // Find a cell to insert before or after + do + { + // Search for a cell to insert before + res = GetCellDataAt(aTable, rowBelowIndex, + colIndex, getter_AddRefs(cell2), + &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, + &actualRowSpan2, &actualColSpan2, &isSelected2); + // If we fail here, it could be because row has bad rowspan values, + // such as all cells having rowspan > 1 (Call FixRowSpan first!) + if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE; + + // Skip over cells spanned from above (like the one we are splitting!) + if (cell2 && startRowIndex2 == rowBelowIndex) + { + if (insertAfter) + { + // New cell isn't first in row, + // so stop after we find the cell just before new cell's column + if ((startColIndex2 + actualColSpan2) == startColIndex) + break; + + // If cell found is AFTER desired new cell colum, + // we have multiple cells with rowspan > 1 that + // prevented us from finding a cell to insert after... + if (startColIndex2 > startColIndex) + { + // ... so instead insert before the cell we found + insertAfter = false; + break; + } + } + else + { + break; // Inserting before, so stop at first cell in row we want to insert into + } + lastCellFound = cell2; + } + // Skip to next available cellmap location + colIndex += std::max(actualColSpan2, 1); + + // Done when past end of total number of columns + if (colIndex > colCount) + break; + + } while(true); + + if (!cell2 && lastCellFound) + { + // Edge case where we didn't find a cell to insert after + // or before because column(s) before desired column + // and all columns after it are spanned from above. + // We can insert after the last cell we found + cell2 = lastCellFound; + insertAfter = true; // Should always be true, but let's be sure + } + + // Reduce rowspan of cell to split + res = SetRowSpan(cell, aRowSpanAbove); + NS_ENSURE_SUCCESS(res, res); + + + // Insert new cell after using the remaining span + // and always get the new cell so we can copy the background color; + nsCOMPtr newCell; + res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, false, getter_AddRefs(newCell)); + NS_ENSURE_SUCCESS(res, res); + if (newCell) + { + if (aNewCell) + { + *aNewCell = newCell.get(); + NS_ADDREF(*aNewCell); + } + res = CopyCellBackgroundColor(newCell, cell2); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SwitchTableCellHeaderType(nsIDOMElement *aSourceCell, nsIDOMElement **aNewCell) +{ + NS_ENSURE_TRUE(aSourceCell, NS_ERROR_NULL_POINTER); + + nsAutoEditBatch beginBatching(this); + // Prevent auto insertion of BR in new cell created by ReplaceContainer + nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); + + nsCOMPtr newNode; + + // Save current selection to restore when done + // This is needed so ReplaceContainer can monitor selection + // when replacing nodes + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + nsAutoSelectionReset selectionResetter(selection, this); + + // Set to the opposite of current type + nsCOMPtr atom = nsEditor::GetTag(aSourceCell); + nsString newCellType( (atom == nsEditProperty::td) ? NS_LITERAL_STRING("th") : NS_LITERAL_STRING("td")); + + // This creates new node, moves children, copies attributes (true) + // and manages the selection! + nsresult res = ReplaceContainer(aSourceCell, address_of(newNode), + newCellType, nullptr, nullptr, true); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(newNode, NS_ERROR_FAILURE); + + // Return the new cell + if (aNewCell) + { + nsCOMPtr newElement = do_QueryInterface(newNode); + *aNewCell = newElement.get(); + NS_ADDREF(*aNewCell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) +{ + nsCOMPtr table; + nsCOMPtr targetCell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + nsCOMPtr cell2; + int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; + bool isSelected2; + + // Get cell, table, etc. at selection anchor node + nsresult res = GetCellContext(nullptr, + getter_AddRefs(table), + getter_AddRefs(targetCell), + nullptr, nullptr, + &startRowIndex, &startColIndex); + NS_ENSURE_SUCCESS(res, res); + if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + nsAutoEditBatch beginBatching(this); + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + + // Note: We dont' use nsSetSelectionAfterTableEdit here so the selection + // is retained after joining. This leaves the target cell selected + // as well as the "non-contiguous" cells, so user can see what happened. + + nsCOMPtr firstCell; + int32_t firstRowIndex, firstColIndex; + res = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex, getter_AddRefs(firstCell)); + NS_ENSURE_SUCCESS(res, res); + + bool joinSelectedCells = false; + if (firstCell) + { + nsCOMPtr secondCell; + res = GetNextSelectedCell(nullptr, getter_AddRefs(secondCell)); + NS_ENSURE_SUCCESS(res, res); + + // If only one cell is selected, join with cell to the right + joinSelectedCells = (secondCell != nullptr); + } + + if (joinSelectedCells) + { + // We have selected cells: Join just contiguous cells + // and just merge contents if not contiguous + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Get spans for cell we will merge into + int32_t firstRowSpan, firstColSpan; + res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan); + NS_ENSURE_SUCCESS(res, res); + + // This defines the last indexes along the "edges" + // of the contiguous block of cells, telling us + // that we can join adjacent cells to the block + // Start with same as the first values, + // then expand as we find adjacent selected cells + int32_t lastRowIndex = firstRowIndex; + int32_t lastColIndex = firstColIndex; + int32_t rowIndex, colIndex; + + // First pass: Determine boundaries of contiguous rectangular block + // that we will join into one cell, + // favoring adjacent cells in the same row + for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) + { + int32_t currentRowCount = rowCount; + // Be sure each row doesn't have rowspan errors + res = FixBadRowSpan(table, rowIndex, rowCount); + NS_ENSURE_SUCCESS(res, res); + // Adjust rowcount by number of rows we removed + lastRowIndex -= (currentRowCount-rowCount); + + bool cellFoundInRow = false; + bool lastRowIsSet = false; + int32_t lastColInRow = 0; + int32_t firstColInRow = firstColIndex; + for (colIndex = firstColIndex; colIndex < colCount; colIndex += std::max(actualColSpan2, 1)) + { + res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2), + &startRowIndex2, &startColIndex2, + &rowSpan2, &colSpan2, + &actualRowSpan2, &actualColSpan2, &isSelected2); + NS_ENSURE_SUCCESS(res, res); + + if (isSelected2) + { + if (!cellFoundInRow) + // We've just found the first selected cell in this row + firstColInRow = colIndex; + + if (rowIndex > firstRowIndex && firstColInRow != firstColIndex) + { + // We're in at least the second row, + // but left boundary is "ragged" (not the same as 1st row's start) + //Let's just end block on previous row + // and keep previous lastColIndex + //TODO: We could try to find the Maximum firstColInRow + // so our block can still extend down more rows? + lastRowIndex = std::max(0,rowIndex - 1); + lastRowIsSet = true; + break; + } + // Save max selected column in this row, including extra colspan + lastColInRow = colIndex + (actualColSpan2-1); + cellFoundInRow = true; + } + else if (cellFoundInRow) + { + // No cell or not selected, but at least one cell in row was found + + if (rowIndex > (firstRowIndex+1) && colIndex <= lastColIndex) + { + // Cell is in a column less than current right border in + // the third or higher selected row, so stop block at the previous row + lastRowIndex = std::max(0,rowIndex - 1); + lastRowIsSet = true; + } + // We're done with this row + break; + } + } // End of column loop + + // Done with this row + if (cellFoundInRow) + { + if (rowIndex == firstRowIndex) + { + // First row always initializes the right boundary + lastColIndex = lastColInRow; + } + + // If we didn't determine last row above... + if (!lastRowIsSet) + { + if (colIndex < lastColIndex) + { + // (don't think we ever get here?) + // Cell is in a column less than current right boundary, + // so stop block at the previous row + lastRowIndex = std::max(0,rowIndex - 1); + } + else + { + // Go on to examine next row + lastRowIndex = rowIndex+1; + } + } + // Use the minimum col we found so far for right boundary + lastColIndex = std::min(lastColIndex, lastColInRow); + } + else + { + // No selected cells in this row -- stop at row above + // and leave last column at its previous value + lastRowIndex = std::max(0,rowIndex - 1); + } + } + + // The list of cells we will delete after joining + nsTArray > deleteList; + + // 2nd pass: Do the joining and merging + for (rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + for (colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan2, 1)) + { + res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2), + &startRowIndex2, &startColIndex2, + &rowSpan2, &colSpan2, + &actualRowSpan2, &actualColSpan2, &isSelected2); + NS_ENSURE_SUCCESS(res, res); + + // If this is 0, we are past last cell in row, so exit the loop + if (actualColSpan2 == 0) + break; + + // Merge only selected cells (skip cell we're merging into, of course) + if (isSelected2 && cell2 != firstCell) + { + if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex && + colIndex >= firstColIndex && colIndex <= lastColIndex) + { + // We are within the join region + // Problem: It is very tricky to delete cells as we merge, + // since that will upset the cellmap + // Instead, build a list of cells to delete and do it later + NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above"); + + if (actualColSpan2 > 1) + { + //Check if cell "hangs" off the boundary because of colspan > 1 + // Use split methods to chop off excess + int32_t extraColSpan = (startColIndex2 + actualColSpan2) - (lastColIndex+1); + if ( extraColSpan > 0) + { + res = SplitCellIntoColumns(table, startRowIndex2, startColIndex2, + actualColSpan2-extraColSpan, extraColSpan, nullptr); + NS_ENSURE_SUCCESS(res, res); + } + } + + res = MergeCells(firstCell, cell2, false); + NS_ENSURE_SUCCESS(res, res); + + // Add cell to list to delete + deleteList.AppendElement(cell2.get()); + } + else if (aMergeNonContiguousContents) + { + // Cell is outside join region -- just merge the contents + res = MergeCells(firstCell, cell2, false); + NS_ENSURE_SUCCESS(res, res); + } + } + } + } + + // All cell contents are merged. Delete the empty cells we accumulated + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) + { + nsIDOMElement *elementPtr = deleteList[i]; + if (elementPtr) + { + nsCOMPtr node = do_QueryInterface(elementPtr); + res = DeleteNode(node); + NS_ENSURE_SUCCESS(res, res); + } + } + // Cleanup selection: remove ranges where cells were deleted + nsCOMPtr selection; + res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + int32_t rangeCount; + res = selection->GetRangeCount(&rangeCount); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr range; + int32_t i; + for (i = 0; i < rangeCount; i++) + { + res = selection->GetRangeAt(i, getter_AddRefs(range)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); + + nsCOMPtr deletedCell; + res = GetCellFromRange(range, getter_AddRefs(deletedCell)); + if (!deletedCell) + { + selection->RemoveRange(range); + rangeCount--; + i--; + } + } + + // Set spans for the cell everthing merged into + res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1); + NS_ENSURE_SUCCESS(res, res); + res = SetColSpan(firstCell, lastColIndex-firstColIndex+1); + NS_ENSURE_SUCCESS(res, res); + + + // Fixup disturbances in table layout + NormalizeTable(table); + } + else + { + // Joining with cell to the right -- get rowspan and colspan data of target cell + res = GetCellDataAt(table, startRowIndex, startColIndex, getter_AddRefs(targetCell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(targetCell, NS_ERROR_NULL_POINTER); + + // Get data for cell to the right + res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, getter_AddRefs(cell2), + &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, + &actualRowSpan2, &actualColSpan2, &isSelected2); + NS_ENSURE_SUCCESS(res, res); + if(!cell2) return NS_OK; // Don't fail if there's no cell + + // sanity check + NS_ASSERTION((startRowIndex >= startRowIndex2),"JoinCells: startRowIndex < startRowIndex2"); + + // Figure out span of merged cell starting from target's starting row + // to handle case of merged cell starting in a row above + int32_t spanAboveMergedCell = startRowIndex - startRowIndex2; + int32_t effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell; + + if (effectiveRowSpan2 > actualRowSpan) + { + // Cell to the right spans into row below target + // Split off portion below target cell's bottom-most row + res = SplitCellIntoRows(table, startRowIndex2, startColIndex2, + spanAboveMergedCell+actualRowSpan, + effectiveRowSpan2-actualRowSpan, nullptr); + NS_ENSURE_SUCCESS(res, res); + } + + // Move contents from cell to the right + // Delete the cell now only if it starts in the same row + // and has enough row "height" + res = MergeCells(targetCell, cell2, + (startRowIndex2 == startRowIndex) && + (effectiveRowSpan2 >= actualRowSpan)); + NS_ENSURE_SUCCESS(res, res); + + if (effectiveRowSpan2 < actualRowSpan) + { + // Merged cell is "shorter" + // (there are cells(s) below it that are row-spanned by target cell) + // We could try splitting those cells, but that's REAL messy, + // so the safest thing to do is NOT really join the cells + return NS_OK; + } + + if( spanAboveMergedCell > 0 ) + { + // Cell we merged started in a row above the target cell + // Reduce rowspan to give room where target cell will extend its colspan + res = SetRowSpan(cell2, spanAboveMergedCell); + NS_ENSURE_SUCCESS(res, res); + } + + // Reset target cell's colspan to encompass cell to the right + res = SetColSpan(targetCell, actualColSpan+actualColSpan2); + NS_ENSURE_SUCCESS(res, res); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::MergeCells(nsCOMPtr aTargetCell, + nsCOMPtr aCellToMerge, + bool aDeleteCellToMerge) +{ + nsCOMPtr targetCell = do_QueryInterface(aTargetCell); + nsCOMPtr cellToMerge = do_QueryInterface(aCellToMerge); + NS_ENSURE_TRUE(targetCell && cellToMerge, NS_ERROR_NULL_POINTER); + + // Prevent rules testing until we're done + nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); + + // Don't need to merge if cell is empty + if (!IsEmptyCell(cellToMerge)) { + // Get index of last child in target cell + // If we fail or don't have children, + // we insert at index 0 + int32_t insertIndex = 0; + + // Start inserting just after last child + uint32_t len = targetCell->GetChildCount(); + if (len == 1 && IsEmptyCell(targetCell)) { + // Delete the empty node + nsIContent* cellChild = targetCell->GetFirstChild(); + nsresult res = DeleteNode(cellChild->AsDOMNode()); + NS_ENSURE_SUCCESS(res, res); + insertIndex = 0; + } else { + insertIndex = (int32_t)len; + } + + // Move the contents + while (cellToMerge->HasChildren()) { + nsCOMPtr cellChild = cellToMerge->GetLastChild()->AsDOMNode(); + nsresult res = DeleteNode(cellChild); + NS_ENSURE_SUCCESS(res, res); + + res = InsertNode(cellChild, aTargetCell, insertIndex); + NS_ENSURE_SUCCESS(res, res); + } + } + + // Delete cells whose contents were moved + if (aDeleteCellToMerge) + return DeleteNode(aCellToMerge); + + return NS_OK; +} + + +NS_IMETHODIMP +nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, int32_t aRowIndex, int32_t& aNewRowCount) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + + int32_t rowCount, colCount; + nsresult res = GetTableSize(aTable, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtrcell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + + int32_t minRowSpan = -1; + int32_t colIndex; + + for( colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan, 1)) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + // NOTE: This is a *real* failure. + // GetCellDataAt passes if cell is missing from cellmap + if(NS_FAILED(res)) return res; + if (!cell) break; + if(rowSpan > 0 && + startRowIndex == aRowIndex && + (rowSpan < minRowSpan || minRowSpan == -1)) + { + minRowSpan = rowSpan; + } + NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan"); + } + if(minRowSpan > 1) + { + // The amount to reduce everyone's rowspan + // so at least one cell has rowspan = 1 + int32_t rowsReduced = minRowSpan - 1; + for(colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan, 1)) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if(NS_FAILED(res)) return res; + // Fixup rowspans only for cells starting in current row + if(cell && rowSpan > 0 && + startRowIndex == aRowIndex && + startColIndex == colIndex ) + { + res = SetRowSpan(cell, rowSpan-rowsReduced); + if(NS_FAILED(res)) return res; + } + NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan"); + } + } + return GetTableSize(aTable, &aNewRowCount, &colCount); +} + +NS_IMETHODIMP +nsHTMLEditor::FixBadColSpan(nsIDOMElement *aTable, int32_t aColIndex, int32_t& aNewColCount) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); + + int32_t rowCount, colCount; + nsresult res = GetTableSize(aTable, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + + int32_t minColSpan = -1; + int32_t rowIndex; + + for( rowIndex = 0; rowIndex < rowCount; rowIndex += std::max(actualRowSpan, 1)) + { + res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + // NOTE: This is a *real* failure. + // GetCellDataAt passes if cell is missing from cellmap + if(NS_FAILED(res)) return res; + if (!cell) break; + if(colSpan > 0 && + startColIndex == aColIndex && + (colSpan < minColSpan || minColSpan == -1)) + { + minColSpan = colSpan; + } + NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan"); + } + if(minColSpan > 1) + { + // The amount to reduce everyone's colspan + // so at least one cell has colspan = 1 + int32_t colsReduced = minColSpan - 1; + for(rowIndex = 0; rowIndex < rowCount; rowIndex += std::max(actualRowSpan, 1)) + { + res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + if(NS_FAILED(res)) return res; + // Fixup colspans only for cells starting in current column + if(cell && colSpan > 0 && + startColIndex == aColIndex && + startRowIndex == rowIndex ) + { + res = SetColSpan(cell, colSpan-colsReduced); + if(NS_FAILED(res)) return res; + } + NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan"); + } + } + return GetTableSize(aTable, &rowCount, &aNewColCount); +} + +NS_IMETHODIMP +nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) +{ + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + nsCOMPtr table; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), + aTable, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + // Don't fail if we didn't find a table + NS_ENSURE_TRUE(table, NS_OK); + + int32_t rowCount, colCount, rowIndex, colIndex; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Save current selection + nsAutoSelectionReset selectionResetter(selection, this); + + nsAutoEditBatch beginBatching(this); + // Prevent auto insertion of BR in new cell until we're done + nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); + + nsCOMPtr cell; + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + + // Scan all cells in each row to detect bad rowspan values + for(rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + res = FixBadRowSpan(table, rowIndex, rowCount); + NS_ENSURE_SUCCESS(res, res); + } + // and same for colspans + for(colIndex = 0; colIndex < colCount; colIndex++) + { + res = FixBadColSpan(table, colIndex, colCount); + NS_ENSURE_SUCCESS(res, res); + } + + // Fill in missing cellmap locations with empty cells + for(rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + nsCOMPtr previousCellInRow; + + for(colIndex = 0; colIndex < colCount; colIndex++) + { + res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + // NOTE: This is a *real* failure. + // GetCellDataAt passes if cell is missing from cellmap + if(NS_FAILED(res)) return res; + if (!cell) + { + //We are missing a cell at a cellmap location +#ifdef DEBUG + printf("NormalizeTable found missing cell at row=%d, col=%d\n", rowIndex, colIndex); +#endif + // Add a cell after the previous Cell in the current row + if(previousCellInRow) + { + // Insert a new cell after (true), and return the new cell to us + res = InsertCell(previousCellInRow, 1, 1, true, false, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + + // Set this so we use returned new "cell" to set previousCellInRow below + if(cell) + startRowIndex = rowIndex; + } else { + // We don't have any cells in this row -- We are really messed up! +#ifdef DEBUG + printf("NormalizeTable found no cells in row=%d, col=%d\n", rowIndex, colIndex); +#endif + return NS_ERROR_FAILURE; + } + } + // Save the last cell found in the same row we are scanning + if(startRowIndex == rowIndex) + { + previousCellInRow = cell; + } + } + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell, + int32_t *aRowIndex, int32_t *aColIndex) +{ + NS_ENSURE_ARG_POINTER(aRowIndex); + *aColIndex=0; // initialize out params + NS_ENSURE_ARG_POINTER(aColIndex); + *aRowIndex=0; + nsresult res=NS_ERROR_NOT_INITIALIZED; + if (!aCell) + { + // Get the selected cell or the cell enclosing the selection anchor + nsCOMPtr cell; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); + if (NS_SUCCEEDED(res) && cell) + aCell = cell; + else + return NS_ERROR_FAILURE; + } + + NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); + nsCOMPtr ps = GetPresShell(); + NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr nodeAsContent( do_QueryInterface(aCell) ); + NS_ENSURE_TRUE(nodeAsContent, NS_ERROR_FAILURE); + // frames are not ref counted, so don't use an nsCOMPtr + nsIFrame *layoutObject = nodeAsContent->GetPrimaryFrame(); + NS_ENSURE_TRUE(layoutObject, NS_ERROR_FAILURE); + + nsITableCellLayout *cellLayoutObject = do_QueryFrame(layoutObject); + NS_ENSURE_TRUE(cellLayoutObject, NS_ERROR_FAILURE); + return cellLayoutObject->GetCellIndexes(*aRowIndex, *aColIndex); +} + +nsTableOuterFrame* +nsHTMLEditor::GetTableFrame(nsIDOMElement* aTable) +{ + NS_ENSURE_TRUE(aTable, nullptr); + + nsCOMPtr nodeAsContent( do_QueryInterface(aTable) ); + NS_ENSURE_TRUE(nodeAsContent, nullptr); + return do_QueryFrame(nodeAsContent->GetPrimaryFrame()); +} + +//Return actual number of cells (a cell with colspan > 1 counts as just 1) +int32_t nsHTMLEditor::GetNumberOfCellsInRow(nsIDOMElement* aTable, int32_t rowIndex) +{ + int32_t cellCount = 0; + nsCOMPtr cell; + int32_t colIndex = 0; + nsresult res; + do { + int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + res = GetCellDataAt(aTable, rowIndex, colIndex, getter_AddRefs(cell), + &startRowIndex, &startColIndex, &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + NS_ENSURE_SUCCESS(res, 0); + if (cell) + { + // Only count cells that start in row we are working with + if (startRowIndex == rowIndex) + cellCount++; + + //Next possible location for a cell + colIndex += actualColSpan; + } + else + colIndex++; + + } while (cell); + + return cellCount; +} + +/* Not scriptable: For convenience in C++ + Use GetTableRowCount and GetTableColumnCount from JavaScript +*/ +NS_IMETHODIMP +nsHTMLEditor::GetTableSize(nsIDOMElement *aTable, + int32_t* aRowCount, int32_t* aColCount) +{ + NS_ENSURE_ARG_POINTER(aRowCount); + NS_ENSURE_ARG_POINTER(aColCount); + nsresult res; + *aRowCount = 0; + *aColCount = 0; + nsCOMPtr table; + // Get the selected talbe or the table enclosing the selection anchor + res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + + nsTableOuterFrame* tableFrame = GetTableFrame(table.get()); + NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE); + + *aRowCount = tableFrame->GetRowCount(); + *aColCount = tableFrame->GetColCount(); + + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::GetCellDataAt(nsIDOMElement* aTable, int32_t aRowIndex, + int32_t aColIndex, nsIDOMElement **aCell, + int32_t* aStartRowIndex, int32_t* aStartColIndex, + int32_t* aRowSpan, int32_t* aColSpan, + int32_t* aActualRowSpan, int32_t* aActualColSpan, + bool* aIsSelected) +{ + NS_ENSURE_ARG_POINTER(aStartRowIndex); + NS_ENSURE_ARG_POINTER(aStartColIndex); + NS_ENSURE_ARG_POINTER(aRowSpan); + NS_ENSURE_ARG_POINTER(aColSpan); + NS_ENSURE_ARG_POINTER(aActualRowSpan); + NS_ENSURE_ARG_POINTER(aActualColSpan); + NS_ENSURE_ARG_POINTER(aIsSelected); + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + + nsresult res=NS_ERROR_FAILURE; + *aStartRowIndex = 0; + *aStartColIndex = 0; + *aRowSpan = 0; + *aColSpan = 0; + *aActualRowSpan = 0; + *aActualColSpan = 0; + *aIsSelected = false; + + *aCell = nullptr; + + if (!aTable) + { + // Get the selected table or the table enclosing the selection anchor + nsCOMPtr table; + res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + if (table) + aTable = table; + else + return NS_ERROR_FAILURE; + } + + nsTableOuterFrame* tableFrame = GetTableFrame(aTable); + NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE); + + nsTableCellFrame* cellFrame = + tableFrame->GetCellFrameAt(aRowIndex, aColIndex); + if (!cellFrame) + return NS_ERROR_FAILURE; + + *aIsSelected = cellFrame->IsSelected(); + cellFrame->GetRowIndex(*aStartRowIndex); + cellFrame->GetColIndex(*aStartColIndex); + *aRowSpan = cellFrame->GetRowSpan(); + *aColSpan = cellFrame->GetColSpan(); + *aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex); + *aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex); + nsCOMPtr domCell = do_QueryInterface(cellFrame->GetContent()); + domCell.forget(aCell); + + return NS_OK; +} + +// When all you want is the cell +NS_IMETHODIMP +nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, int32_t aRowIndex, int32_t aColIndex, nsIDOMElement **aCell) +{ + NS_ENSURE_ARG_POINTER(aCell); + *aCell = nullptr; + + if (!aTable) + { + // Get the selected table or the table enclosing the selection anchor + nsCOMPtr table; + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + aTable = table; + } + + nsTableOuterFrame* tableFrame = GetTableFrame(aTable); + if (!tableFrame) + return NS_ERROR_FAILURE; + + nsCOMPtr domCell = + do_QueryInterface(tableFrame->GetCellAt(aRowIndex, aColIndex)); + domCell.forget(aCell); + + return NS_OK; +} + +// When all you want are the rowspan and colspan (not exposed in nsITableEditor) +NS_IMETHODIMP +nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t& aActualRowSpan, int32_t& aActualColSpan) +{ + nsTableOuterFrame* tableFrame = GetTableFrame(aTable); + if (!tableFrame) + return NS_ERROR_FAILURE; + + aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex); + aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex); + + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLEditor::GetCellContext(nsISelection **aSelection, + nsIDOMElement **aTable, + nsIDOMElement **aCell, + nsIDOMNode **aCellParent, int32_t *aCellOffset, + int32_t *aRowIndex, int32_t *aColIndex) +{ + // Initialize return pointers + if (aSelection) *aSelection = nullptr; + if (aTable) *aTable = nullptr; + if (aCell) *aCell = nullptr; + if (aCellParent) *aCellParent = nullptr; + if (aCellOffset) *aCellOffset = 0; + if (aRowIndex) *aRowIndex = 0; + if (aColIndex) *aColIndex = 0; + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + if (aSelection) + { + *aSelection = selection.get(); + NS_ADDREF(*aSelection); + } + nsCOMPtr table; + nsCOMPtr cell; + + // Caller may supply the cell... + if (aCell && *aCell) + cell = *aCell; + + // ...but if not supplied, + // get cell if it's the child of selection anchor node, + // or get the enclosing by a cell + if (!cell) + { + // Find a selected or enclosing table element + nsCOMPtr cellOrTableElement; + int32_t selectedCount; + nsAutoString tagName; + res = GetSelectedOrParentTableElement(tagName, &selectedCount, + getter_AddRefs(cellOrTableElement)); + NS_ENSURE_SUCCESS(res, res); + if (tagName.EqualsLiteral("table")) + { + // We have a selected table, not a cell + if (aTable) + { + *aTable = cellOrTableElement.get(); + NS_ADDREF(*aTable); + } + return NS_OK; + } + if (!tagName.EqualsLiteral("td")) + return NS_EDITOR_ELEMENT_NOT_FOUND; + + // We found a cell + cell = cellOrTableElement; + } + if (aCell) + { + *aCell = cell.get(); + NS_ADDREF(*aCell); + } + + // Get containing table + res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + // Cell must be in a table, so fail if not found + NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); + if (aTable) + { + *aTable = table.get(); + NS_ADDREF(*aTable); + } + + // Get the rest of the related data only if requested + if (aRowIndex || aColIndex) + { + int32_t rowIndex, colIndex; + // Get current cell location so we can put caret back there when done + res = GetCellIndexes(cell, &rowIndex, &colIndex); + if(NS_FAILED(res)) return res; + if (aRowIndex) *aRowIndex = rowIndex; + if (aColIndex) *aColIndex = colIndex; + } + if (aCellParent) + { + nsCOMPtr cellParent; + // Get the immediate parent of the cell + res = cell->GetParentNode(getter_AddRefs(cellParent)); + NS_ENSURE_SUCCESS(res, res); + // Cell has to have a parent, so fail if not found + NS_ENSURE_TRUE(cellParent, NS_ERROR_FAILURE); + + *aCellParent = cellParent.get(); + NS_ADDREF(*aCellParent); + + if (aCellOffset) { + *aCellOffset = GetChildOffset(cell, cellParent); + } + } + + return res; +} + +nsresult +nsHTMLEditor::GetCellFromRange(nsIDOMRange *aRange, nsIDOMElement **aCell) +{ + // Note: this might return a node that is outside of the range. + // Use carefully. + NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER); + + *aCell = nullptr; + + nsCOMPtr startParent; + nsresult res = aRange->GetStartContainer(getter_AddRefs(startParent)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); + + int32_t startOffset; + res = aRange->GetStartOffset(&startOffset); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr childNode = GetChildAt(startParent, startOffset); + // This means selection is probably at a text node (or end of doc?) + if (!childNode) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr endParent; + res = aRange->GetEndContainer(getter_AddRefs(endParent)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); + + int32_t endOffset; + res = aRange->GetEndOffset(&endOffset); + NS_ENSURE_SUCCESS(res, res); + + // If a cell is deleted, the range is collapse + // (startOffset == endOffset) + // so tell caller the cell wasn't found + if (startParent == endParent && + endOffset == startOffset+1 && + nsHTMLEditUtils::IsTableCell(childNode)) + { + // Should we also test if frame is selected? (Use GetCellDataAt()) + // (Let's not for now -- more efficient) + nsCOMPtr cellElement = do_QueryInterface(childNode); + *aCell = cellElement.get(); + NS_ADDREF(*aCell); + return NS_OK; + } + return NS_EDITOR_ELEMENT_NOT_FOUND; +} + +NS_IMETHODIMP +nsHTMLEditor::GetFirstSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + *aCell = nullptr; + if (aRange) *aRange = nullptr; + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + nsCOMPtr range; + res = selection->GetRangeAt(0, getter_AddRefs(range)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); + + mSelectedCellIndex = 0; + + res = GetCellFromRange(range, aCell); + // Failure here probably means selection is in a text node, + // so there's no selected cell + if (NS_FAILED(res)) { + return NS_EDITOR_ELEMENT_NOT_FOUND; + } + // No cell means range was collapsed (cell was deleted) + if (!*aCell) { + return NS_EDITOR_ELEMENT_NOT_FOUND; + } + + if (aRange) + { + *aRange = range.get(); + NS_ADDREF(*aRange); + } + + // Setup for next cell + mSelectedCellIndex = 1; + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::GetNextSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + *aCell = nullptr; + if (aRange) *aRange = nullptr; + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + int32_t rangeCount; + res = selection->GetRangeCount(&rangeCount); + NS_ENSURE_SUCCESS(res, res); + + // Don't even try if index exceeds range count + if (mSelectedCellIndex >= rangeCount) + return NS_EDITOR_ELEMENT_NOT_FOUND; + + // Scan through ranges to find next valid selected cell + nsCOMPtr range; + for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++) + { + res = selection->GetRangeAt(mSelectedCellIndex, getter_AddRefs(range)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); + + res = GetCellFromRange(range, aCell); + // Failure here means the range doesn't contain a cell + NS_ENSURE_SUCCESS(res, NS_EDITOR_ELEMENT_NOT_FOUND); + + // We found a selected cell + if (*aCell) break; +#ifdef DEBUG_cmanske + else + printf("GetNextSelectedCell: Collapsed range found\n"); +#endif + + // If we didn't find a cell, continue to next range in selection + } + // No cell means all remaining ranges were collapsed (cells were deleted) + NS_ENSURE_TRUE(*aCell, NS_EDITOR_ELEMENT_NOT_FOUND); + + if (aRange) + { + *aRange = range.get(); + NS_ADDREF(*aRange); + } + + // Setup for next cell + mSelectedCellIndex++; + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::GetFirstSelectedCellInTable(int32_t *aRowIndex, int32_t *aColIndex, nsIDOMElement **aCell) +{ + NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); + *aCell = nullptr; + if (aRowIndex) + *aRowIndex = 0; + if (aColIndex) + *aColIndex = 0; + + nsCOMPtr cell; + nsresult res = GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); + + *aCell = cell.get(); + NS_ADDREF(*aCell); + + // Also return the row and/or column if requested + if (aRowIndex || aColIndex) + { + int32_t startRowIndex, startColIndex; + res = GetCellIndexes(cell, &startRowIndex, &startColIndex); + if(NS_FAILED(res)) return res; + + if (aRowIndex) + *aRowIndex = startRowIndex; + + if (aColIndex) + *aColIndex = startColIndex; + } + + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, int32_t aRow, int32_t aCol, + int32_t aDirection, bool aSelected) +{ + NS_ENSURE_TRUE(aTable, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtrselection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + + if (!selection) + { +#ifdef DEBUG_cmanske + printf("Selection not found after table manipulation!\n"); +#endif + return NS_ERROR_FAILURE; + } + + nsCOMPtr cell; + bool done = false; + do { + res = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell)); + if (NS_SUCCEEDED(res)) + { + if (cell) + { + if (aSelected) + { + // Reselect the cell + return SelectElement(cell); + } + else + { + // Set the caret to deepest first child + // but don't go into nested tables + // TODO: Should we really be placing the caret at the END + // of the cell content? + return CollapseSelectionToDeepestNonTableFirstChild(selection, cell); + } + } else { + // Setup index to find another cell in the + // direction requested, but move in + // other direction if already at beginning of row or column + switch (aDirection) + { + case ePreviousColumn: + if (aCol == 0) + { + if (aRow > 0) + aRow--; + else + done = true; + } + else + aCol--; + break; + case ePreviousRow: + if (aRow == 0) + { + if (aCol > 0) + aCol--; + else + done = true; + } + else + aRow--; + break; + default: + done = true; + } + } + } + else + break; + } while (!done); + + // We didn't find a cell + // Set selection to just before the table + nsCOMPtr tableParent; + res = aTable->GetParentNode(getter_AddRefs(tableParent)); + if(NS_SUCCEEDED(res) && tableParent) + { + int32_t tableOffset = GetChildOffset(aTable, tableParent); + return selection->Collapse(tableParent, tableOffset); + } + // Last resort: Set selection to start of doc + // (it's very bad to not have a valid selection!) + return SetSelectionAtDocumentStart(selection); +} + +NS_IMETHODIMP +nsHTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName, + int32_t *aSelectedCount, + nsIDOMElement** aTableElement) +{ + NS_ENSURE_ARG_POINTER(aTableElement); + NS_ENSURE_ARG_POINTER(aSelectedCount); + *aTableElement = nullptr; + aTagName.Truncate(); + *aSelectedCount = 0; + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + // Try to get the first selected cell + nsCOMPtr tableOrCellElement; + res = GetFirstSelectedCell(nullptr, getter_AddRefs(tableOrCellElement)); + NS_ENSURE_SUCCESS(res, res); + + NS_NAMED_LITERAL_STRING(tdName, "td"); + + if (tableOrCellElement) + { + // Each cell is in its own selection range, + // so count signals multiple-cell selection + res = selection->GetRangeCount(aSelectedCount); + NS_ENSURE_SUCCESS(res, res); + aTagName = tdName; + } + else + { + nsCOMPtr anchorNode; + res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); + if(NS_FAILED(res)) return res; + NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE); + + nsCOMPtr selectedNode; + + // Get child of anchor node, if exists + bool hasChildren; + anchorNode->HasChildNodes(&hasChildren); + + if (hasChildren) + { + int32_t anchorOffset; + res = selection->GetAnchorOffset(&anchorOffset); + NS_ENSURE_SUCCESS(res, res); + selectedNode = GetChildAt(anchorNode, anchorOffset); + if (!selectedNode) + { + selectedNode = anchorNode; + // If anchor doesn't have a child, we can't be selecting a table element, + // so don't do the following: + } + else + { + nsCOMPtr atom = nsEditor::GetTag(selectedNode); + + if (atom == nsEditProperty::td) + { + tableOrCellElement = do_QueryInterface(selectedNode); + aTagName = tdName; + // Each cell is in its own selection range, + // so count signals multiple-cell selection + res = selection->GetRangeCount(aSelectedCount); + NS_ENSURE_SUCCESS(res, res); + } + else if (atom == nsEditProperty::table) + { + tableOrCellElement = do_QueryInterface(selectedNode); + aTagName.AssignLiteral("table"); + *aSelectedCount = 1; + } + else if (atom == nsEditProperty::tr) + { + tableOrCellElement = do_QueryInterface(selectedNode); + aTagName.AssignLiteral("tr"); + *aSelectedCount = 1; + } + } + } + if (!tableOrCellElement) + { + // Didn't find a table element -- find a cell parent + res = GetElementOrParentByTagName(tdName, anchorNode, getter_AddRefs(tableOrCellElement)); + if(NS_FAILED(res)) return res; + if (tableOrCellElement) + aTagName = tdName; + } + } + if (tableOrCellElement) + { + *aTableElement = tableOrCellElement.get(); + NS_ADDREF(*aTableElement); + } + return res; +} + +NS_IMETHODIMP +nsHTMLEditor::GetSelectedCellsType(nsIDOMElement *aElement, uint32_t *aSelectionType) +{ + NS_ENSURE_ARG_POINTER(aSelectionType); + *aSelectionType = 0; + + // Be sure we have a table element + // (if aElement is null, this uses selection's anchor node) + nsCOMPtr table; + + nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aElement, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(res, res); + + int32_t rowCount, colCount; + res = GetTableSize(table, &rowCount, &colCount); + NS_ENSURE_SUCCESS(res, res); + + // Traverse all selected cells + nsCOMPtr selectedCell; + res = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell)); + NS_ENSURE_SUCCESS(res, res); + if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK; + + // We have at least one selected cell, so set return value + *aSelectionType = nsISelectionPrivate::TABLESELECTION_CELL; + + // Store indexes of each row/col to avoid duplication of searches + nsTArray indexArray; + + bool allCellsInRowAreSelected = false; + bool allCellsInColAreSelected = false; + while (NS_SUCCEEDED(res) && selectedCell) + { + // Get the cell's location in the cellmap + int32_t startRowIndex, startColIndex; + res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex); + if(NS_FAILED(res)) return res; + + if (!indexArray.Contains(startColIndex)) + { + indexArray.AppendElement(startColIndex); + allCellsInRowAreSelected = AllCellsInRowSelected(table, startRowIndex, colCount); + // We're done as soon as we fail for any row + if (!allCellsInRowAreSelected) break; + } + res = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell)); + } + + if (allCellsInRowAreSelected) + { + *aSelectionType = nsISelectionPrivate::TABLESELECTION_ROW; + return NS_OK; + } + // Test for columns + + // Empty the indexArray + indexArray.Clear(); + + // Start at first cell again + res = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell)); + while (NS_SUCCEEDED(res) && selectedCell) + { + // Get the cell's location in the cellmap + int32_t startRowIndex, startColIndex; + res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex); + if(NS_FAILED(res)) return res; + + if (!indexArray.Contains(startRowIndex)) + { + indexArray.AppendElement(startColIndex); + allCellsInColAreSelected = AllCellsInColumnSelected(table, startColIndex, rowCount); + // We're done as soon as we fail for any column + if (!allCellsInRowAreSelected) break; + } + res = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell)); + } + if (allCellsInColAreSelected) + *aSelectionType = nsISelectionPrivate::TABLESELECTION_COLUMN; + + return NS_OK; +} + +bool +nsHTMLEditor::AllCellsInRowSelected(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aNumberOfColumns) +{ + NS_ENSURE_TRUE(aTable, false); + + int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + + for( int32_t col = 0; col < aNumberOfColumns; col += std::max(actualColSpan, 1)) + { + nsCOMPtr cell; + nsresult res = GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + + NS_ENSURE_SUCCESS(res, false); + // If no cell, we may have a "ragged" right edge, + // so return TRUE only if we already found a cell in the row + NS_ENSURE_TRUE(cell, (col > 0) ? true : false); + + // Return as soon as a non-selected cell is found + NS_ENSURE_TRUE(isSelected, false); + + NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in AllCellsInRowSelected"); + } + return true; +} + +bool +nsHTMLEditor::AllCellsInColumnSelected(nsIDOMElement *aTable, int32_t aColIndex, int32_t aNumberOfRows) +{ + NS_ENSURE_TRUE(aTable, false); + + int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + bool isSelected; + + for( int32_t row = 0; row < aNumberOfRows; row += std::max(actualRowSpan, 1)) + { + nsCOMPtr cell; + nsresult res = GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell), + &curStartRowIndex, &curStartColIndex, + &rowSpan, &colSpan, + &actualRowSpan, &actualColSpan, &isSelected); + + NS_ENSURE_SUCCESS(res, false); + // If no cell, we must have a "ragged" right edge on the last column + // so return TRUE only if we already found a cell in the row + NS_ENSURE_TRUE(cell, (row > 0) ? true : false); + + // Return as soon as a non-selected cell is found + NS_ENSURE_TRUE(isSelected, false); + } + return true; +} + +bool +nsHTMLEditor::IsEmptyCell(dom::Element* aCell) +{ + MOZ_ASSERT(aCell); + + // Check if target only contains empty text node or
+ nsCOMPtr cellChild = aCell->GetFirstChild(); + if (!cellChild) { + return false; + } + + nsCOMPtr nextChild = cellChild->GetNextSibling(); + if (nextChild) { + return false; + } + + // We insert a single break into a cell by default + // to have some place to locate a cursor -- it is dispensable + if (cellChild->IsElement() && cellChild->AsElement()->IsHTML(nsGkAtoms::br)) { + return true; + } + + bool isEmpty; + // Or check if no real content + nsresult rv = IsEmptyNode(cellChild, &isEmpty, false, false); + NS_ENSURE_SUCCESS(rv, false); + return isEmpty; +}