Bug 1484128 - part 1: Create HTMLEditor::GetFirstSelectedTableCellElement() for internal use of HTMLEditor::GetFirstSelectedCell() r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 24 Aug 2018 08:29:12 +0000
changeset 481642 90f962c6f3dfefc79d0d6baf6e43e52455ee7b06
parent 481641 7f45ff1131eb91c279120db47194cb4866e9a6f9
child 481643 123eca2de8b83e4519a911d486dcfb3100a9b440
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersm_kato
bugs1484128
milestone63.0a1
Bug 1484128 - part 1: Create HTMLEditor::GetFirstSelectedTableCellElement() for internal use of HTMLEditor::GetFirstSelectedCell() r=m_kato HTMLEditor::GetFirstSelectedCell() is an XPCOM method, but used internally a lot. Therefore, we should create a non-virtual method for internal use. This patch creates HTMLEditor::GetFirstSelectedTableCellElement(), and it won't return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND since nobody needs the value. It's enough to check whether the result is nullptr without error for any callers. Differential Revision: https://phabricator.services.mozilla.com/D4060
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/HTMLTableEditor.cpp
editor/nsITableEditor.idl
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -2345,28 +2345,31 @@ HTMLEditRules::WillDeleteSelection(nsIEd
 
   // If there is only bogus content, cancel the operation
   if (mBogusNode) {
     *aCancel = true;
     return NS_OK;
   }
 
   // First check for table selection mode.  If so, hand off to table editor.
-  RefPtr<Element> cell;
-  nsresult rv =
-    HTMLEditorRef().GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  if (NS_SUCCEEDED(rv) && cell) {
-    rv = HTMLEditorRef().DeleteTableCellContents();
+  ErrorResult error;
+  RefPtr<Element> cellElement =
+    HTMLEditorRef().GetFirstSelectedTableCellElement(SelectionRef(),
+                                                     error);
+  if (cellElement) {
+    error.SuppressException();
+    nsresult rv = HTMLEditorRef().DeleteTableCellContents();
     if (NS_WARN_IF(!CanHandleEditAction())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     *aHandled = true;
     return rv;
   }
-  cell = nullptr;
+  nsresult rv = error.StealNSResult();
+  cellElement = nullptr;
 
   // origCollapsed is used later to determine whether we should join blocks. We
   // don't really care about bCollapsed because it will be modified by
   // ExtendSelectionForDelete later. TryToJoinBlocksWithTransaction() should
   // happen if the original selection is collapsed and the cursor is at the end
   // of a block element, in which case ExtendSelectionForDelete would always
   // make the selection not collapsed.
   bool join = false;
@@ -2438,17 +2441,18 @@ HTMLEditRules::WillDeleteSelection(nsIEd
     } else {
       wsObj.PriorVisibleNode(startPoint,
                              address_of(visNode), &visOffset, &wsType);
     }
 
     if (!visNode) {
       // Can't find anything to delete!
       *aCancel = true;
-      // XXX This is the result of HTMLEditorRef().GetFirstSelectedCell().
+      // XXX This is the result of
+      //     HTMLEditorRef().GetFirstSelectedTableCellElement().
       //     The value could be both an error and NS_OK.
       return rv;
     }
 
     if (wsType == WSType::normalWS) {
       // We found some visible ws to delete.  Let ws code handle it.
       *aHandled = true;
       if (aAction == nsIEditor::eNext) {
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2998,28 +2998,41 @@ HTMLEditor::SetHTMLBackgroundColorWithTr
                                                 getter_AddRefs(element));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool setColor = !aColor.IsEmpty();
 
   RefPtr<nsAtom> bgColorAtom = NS_Atomize("bgcolor");
   if (element) {
     if (selectedCount > 0) {
-      // Traverse all selected cells
-      RefPtr<Element> cell;
-      rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-      if (NS_SUCCEEDED(rv) && cell) {
-        while (cell) {
-          rv = setColor ?
-                 SetAttributeWithTransaction(*cell, *bgColorAtom, aColor) :
-                 RemoveAttributeWithTransaction(*cell, *bgColorAtom);
+      RefPtr<Selection> selection = GetSelection();
+      if (NS_WARN_IF(!selection)) {
+        return NS_ERROR_FAILURE;
+      }
+      IgnoredErrorResult ignoredError;
+      RefPtr<Element> cellElement =
+        GetFirstSelectedTableCellElement(*selection, ignoredError);
+      if (cellElement) {
+        if (setColor) {
+          while (cellElement) {
+            rv =
+              SetAttributeWithTransaction(*cellElement, *bgColorAtom, aColor);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              return rv;
+            }
+            GetNextSelectedCell(nullptr, getter_AddRefs(cellElement));
+          }
+          return NS_OK;
+        }
+        while (cellElement) {
+          rv = RemoveAttributeWithTransaction(*cellElement, *bgColorAtom);
           if (NS_FAILED(rv)) {
             return rv;
           }
-          GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+          GetNextSelectedCell(nullptr, getter_AddRefs(cellElement));
         }
         return NS_OK;
       }
     }
     // If we failed to find a cell, fall through to use originally-found element
   } else {
     // No table element -- set the background color on the body tag
     element = GetRoot();
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -525,16 +525,38 @@ protected: // May be called by friends.
    * If Selection selects only one element node, returns the element node.
    * If Selection is only one range, returns common ancestor of the range.
    * XXX If there are two or more Selection ranges, this returns parent node
    *     of start container of a range which starts with different node from
    *     start container of the first range.
    */
   Element* GetSelectionContainerElement(Selection& aSelection) const;
 
+  /**
+   * GetFirstSelectedTableCellElement() returns a <td> or <th> element if
+   * first range of Selection (i.e., result of Selection::GetRangeAt(0))
+   * selects a <td> element or <th> element.  Even if Selection is in
+   * a cell element, this returns nullptr.  And even if 2nd or later
+   * range of Selection selects a cell element, also returns nullptr.
+   * Note that when this looks for a cell element, this resets the internal
+   * index of ranges of Selection.  When you call GetNextSelectedCell() after
+   * a call of this, it'll return 2nd selected cell if there is.
+   *
+   * @param aSelection          Selection for this editor.
+   * @param aRv                 Returns error if there is no selection or
+   *                            first range of Selection is unexpected.
+   * @return                    A <td> or <th> element is selected by first
+   *                            range of Selection.  Note that the range must
+   *                            be: startContaienr and endContainer are same
+   *                            <tr> element, startOffset + 1 equals endOffset.
+   */
+  already_AddRefed<Element>
+  GetFirstSelectedTableCellElement(Selection& aSelection,
+                                   ErrorResult& aRv) const;
+
   void IsNextCharInNodeWhitespace(nsIContent* aContent,
                                   int32_t aOffset,
                                   bool* outIsSpace,
                                   bool* outIsNBSP,
                                   nsIContent** outNode = nullptr,
                                   int32_t* outOffset = 0);
   void IsPrevCharInNodeWhitespace(nsIContent* aContent,
                                   int32_t aOffset,
@@ -1209,17 +1231,17 @@ protected: // Shouldn't be used by frien
 
   nsresult GetCSSBackgroundColorState(bool* aMixed, nsAString& aOutColor,
                                       bool aBlockLevel);
   nsresult GetHTMLBackgroundColorState(bool* aMixed, nsAString& outColor);
 
   nsresult GetLastCellInRow(nsINode* aRowNode,
                             nsINode** aCellNode);
 
-  nsresult GetCellFromRange(nsRange* aRange, Element** aCell);
+  static nsresult GetCellFromRange(nsRange* aRange, Element** aCell);
 
   /**
    * This sets background on the appropriate container element (table, cell,)
    * or calls into nsTextEditor to set the page background.
    */
   nsresult SetCSSBackgroundColorWithTransaction(const nsAString& aColor);
   nsresult SetHTMLBackgroundColorWithTransaction(const nsAString& aColor);
 
@@ -1850,18 +1872,19 @@ protected:
   RefPtr<TypeInState> mTypeInState;
   RefPtr<ComposerCommandsUpdater> mComposerCommandsUpdater;
 
   bool mCRInParagraphCreatesParagraph;
 
   bool mCSSAware;
   UniquePtr<CSSEditUtils> mCSSEditUtils;
 
-  // Used by GetFirstSelectedCell and GetNextSelectedCell
-  int32_t  mSelectedCellIndex;
+  // mSelectedCellIndex is reset by GetFirstSelectedTableCellElement(),
+  // then, it'll be referred and incremented by GetNextSelectedCell().
+  mutable int32_t mSelectedCellIndex;
 
   nsString mLastStyleSheetURL;
   nsString mLastOverrideStyleSheetURL;
 
   // Maintain a list of associated style sheets and their urls.
   nsTArray<nsString> mStyleSheetURLs;
   nsTArray<RefPtr<StyleSheet>> mStyleSheets;
 
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -198,17 +198,19 @@ HTMLEditor::DoInsertHTMLWithContext(cons
   CommitComposition();
   AutoPlaceholderBatch beginBatching(this);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::ePasteHTMLContent,
                                       nsIEditor::eNext);
 
   // Get selection
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_STATE(selection);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // create a dom document fragment that represents the structure to paste
   nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
   int32_t streamStartOffset = 0, streamEndOffset = 0;
 
   nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
                                            address_of(fragmentAsNode),
                                            address_of(streamStartParent),
@@ -277,19 +279,20 @@ HTMLEditor::DoInsertHTMLWithContext(cons
       }
     }
     return NS_OK;
   }
 
   // Are there any table elements in the list?
   // check for table cell selection mode
   bool cellSelectionMode = false;
-  RefPtr<Element> cell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  if (NS_SUCCEEDED(rv) && cell) {
+  IgnoredErrorResult ignoredError;
+  RefPtr<Element> cellElement =
+    GetFirstSelectedTableCellElement(*selection, ignoredError);
+  if (cellElement) {
     cellSelectionMode = true;
   }
 
   if (cellSelectionMode) {
     // do we have table content to paste?  If so, we want to delete
     // the selected table cells and replace with new table elements;
     // but if not we want to delete _contents_ of cells and replace
     // with non-table elements.  Use cellSelectionMode bool to
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -759,33 +759,36 @@ HTMLEditor::DeleteTableCell(int32_t aNum
   }
 
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   // When 2 or more cells are selected, ignore aNumber and use selected cells.
-  if (firstCell && selection->RangeCount() > 1) {
+  if (firstSelectedCellElement && selection->RangeCount() > 1) {
     ErrorResult error;
     TableSize tableSize(*this, *table, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    cell = firstCell;
+    cell = firstSelectedCellElement;
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
 
     // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
     // destructor
     AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                                startColIndex, ePreviousColumn,
                                                false);
@@ -971,43 +974,46 @@ HTMLEditor::DeleteTableCellContents()
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
   //Don't let Rules System change the selection
   AutoTransactionsConserveSelection dontChangeSelection(*this);
 
-
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-
-  if (firstCell) {
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  if (firstSelectedCellElement) {
     ErrorResult error;
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    cell = firstCell;
+    cell = firstSelectedCellElement;
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
 
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousColumn,
                                              false);
 
   while (cell) {
     DeleteCellContents(cell);
-    if (firstCell) {
+    if (firstSelectedCellElement) {
       // We doing a selected cells, so do all of them
-      rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsresult rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     } else {
       cell = nullptr;
     }
   }
   return NS_OK;
 }
 
 nsresult
@@ -1060,41 +1066,43 @@ HTMLEditor::DeleteTableColumn(int32_t aN
   aNumber = std::min(aNumber, (tableSize.mColumnCount - startColIndex));
 
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
   // Test if deletion is controlled by selected cells
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   uint32_t rangeCount = selection->RangeCount();
 
-  if (firstCell && rangeCount > 1) {
-    CellIndexes firstCellIndexes(*firstCell, error);
+  if (firstSelectedCellElement && rangeCount > 1) {
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
   //We control selection resetting after the insert...
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousRow,
                                              false);
 
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Use selected cells to determine what rows to delete
-    cell = firstCell;
+    cell = firstSelectedCellElement;
 
     while (cell) {
-      if (cell != firstCell) {
+      if (cell != firstSelectedCellElement) {
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
         startRowIndex = cellIndexes.mRow;
         startColIndex = cellIndexes.mColumn;
       }
       // Find the next cell in a different column
@@ -1245,56 +1253,58 @@ HTMLEditor::DeleteTableRow(int32_t aNumb
   }
 
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   uint32_t rangeCount = selection->RangeCount();
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Fetch indexes again - may be different for selected cells
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
 
   //We control selection resetting after the insert...
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousRow,
                                              false);
   // Don't change selection during deletions
   AutoTransactionsConserveSelection dontChangeSelection(*this);
 
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Use selected cells to determine what rows to delete
-    cell = firstCell;
+    cell = firstSelectedCellElement;
 
     while (cell) {
-      if (cell != firstCell) {
+      if (cell != firstSelectedCellElement) {
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
         startRowIndex = cellIndexes.mRow;
         startColIndex = cellIndexes.mColumn;
       }
       // Find the next cell in a different row
       // to continue after we delete this row
       int32_t nextRow = startRowIndex;
       while (nextRow == startRowIndex) {
-        rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+        nsresult rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
         NS_ENSURE_SUCCESS(rv, rv);
         if (!cell) {
           break;
         }
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
@@ -1304,17 +1314,17 @@ HTMLEditor::DeleteTableRow(int32_t aNumb
       // Delete entire row
       rv = DeleteRow(table, startRowIndex);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   } else {
     // Check for counts too high
     aNumber = std::min(aNumber, (tableSize.mRowCount - startRowIndex));
     for (int32_t i = 0; i < aNumber; i++) {
-      rv = DeleteRow(table, startRowIndex);
+      nsresult rv = DeleteRow(table, startRowIndex);
       // If failed in current row, try the next
       if (NS_FAILED(rv)) {
         startRowIndex++;
       }
 
       // Check if there's a cell in the "next" row
       cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
       if (!cell) {
@@ -1531,43 +1541,48 @@ HTMLEditor::SelectBlockOfCells(Element* 
     std::min(startCellIndexes.mRow, endCellIndexes.mRow);
   int32_t maxColumn =
     std::max(startCellIndexes.mColumn, endCellIndexes.mColumn);
   int32_t maxRow =
     std::max(startCellIndexes.mRow, endCellIndexes.mRow);
 
   RefPtr<Element> cell;
   int32_t currentRowIndex, currentColIndex;
-  RefPtr<nsRange> range;
-  nsresult rv =
-    GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
+  cell = GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (!cell) {
     return NS_OK;
   }
-
+  RefPtr<nsRange> range = selection->GetRangeAt(0);
+  MOZ_ASSERT(range);
   while (cell) {
     CellIndexes currentCellIndexes(*cell, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     if (currentCellIndexes.mRow < maxRow ||
         currentCellIndexes.mRow > maxRow ||
         currentCellIndexes.mColumn < maxColumn ||
         currentCellIndexes.mColumn > maxColumn) {
       selection->RemoveRange(*range, IgnoreErrors());
       // Since we've removed the range, decrement pointer to next range
       mSelectedCellIndex--;
     }
-    rv = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsresult rv =
+      GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan;
   bool    isSelected;
+  nsresult rv = NS_OK;
   for (int32_t row = minRow; row <= maxRow; row++) {
     for (int32_t col = minColumn; col <= maxColumn;
         col += std::max(actualColSpan, 1)) {
       rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
                          &currentRowIndex, &currentColIndex,
                          &rowSpan, &colSpan,
                          &actualRowSpan, &actualColSpan, &isSelected);
       if (NS_FAILED(rv)) {
@@ -2360,17 +2375,17 @@ HTMLEditor::JoinTableCells(bool aMergeNo
     uint32_t rangeCount = selection->RangeCount();
 
     RefPtr<nsRange> range;
     for (uint32_t i = 0; i < rangeCount; i++) {
       range = selection->GetRangeAt(i);
       NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
 
       RefPtr<Element> deletedCell;
-      GetCellFromRange(range, getter_AddRefs(deletedCell));
+      HTMLEditor::GetCellFromRange(range, getter_AddRefs(deletedCell));
       if (!deletedCell) {
         selection->RemoveRange(*range, IgnoreErrors());
         rangeCount--;
         i--;
       }
     }
 
     // Set spans for the cell everthing merged into
@@ -3215,16 +3230,17 @@ HTMLEditor::GetCellContext(Selection** a
     // Now it's safe to hand over the reference to cellParent, since
     // we don't need it anymore.
     cellParent.forget(aCellParent);
   }
 
   return NS_OK;
 }
 
+// static
 nsresult
 HTMLEditor::GetCellFromRange(nsRange* aRange,
                              Element** aCell)
 {
   // Note: this might return a node that is outside of the range.
   // Use carefully.
   NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER);
 
@@ -3259,54 +3275,97 @@ HTMLEditor::GetCellFromRange(nsRange* aR
     RefPtr<Element> cellElement = childNode->AsElement();
     cellElement.forget(aCell);
     return NS_OK;
   }
   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
 }
 
 NS_IMETHODIMP
-HTMLEditor::GetFirstSelectedCell(nsRange** aRange,
-                                 Element** aCell)
+HTMLEditor::GetFirstSelectedCell(nsRange** aFirstSelectedRange,
+                                 Element** aFirstSelectedCellElement)
 {
-  NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
-  *aCell = nullptr;
-  if (aRange) {
-    *aRange = nullptr;
+  if (NS_WARN_IF(!aFirstSelectedCellElement)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
+  *aFirstSelectedCellElement = nullptr;
+  if (aFirstSelectedRange) {
+    *aFirstSelectedRange = nullptr;
+  }
+
+  // XXX Oddly, when there is no ranges of Selection, we return error.
+  //     However, despite of that we return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
+  //     when first range of Selection does not select a table cell element.
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
-
-  RefPtr<nsRange> range = selection->GetRangeAt(0);
-  NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
-
-  mSelectedCellIndex = 0;
-
-  nsresult rv = GetCellFromRange(range, aCell);
-  // Failure here probably means selection is in a text node,
-  //  so there's no selected cell
-  if (NS_FAILED(rv)) {
-    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
   }
-  // No cell means range was collapsed (cell was deleted)
-  if (!*aCell) {
-    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+
+  if (!firstSelectedCellElement) {
+    // Just not found.  Don't return error.
+    return NS_OK;
   }
-
-  if (aRange) {
-    range.forget(aRange);
+  firstSelectedCellElement.forget(aFirstSelectedCellElement);
+
+  if (aFirstSelectedRange) {
+    // Returns the first range only when the caller requested the range.
+    RefPtr<nsRange> firstRange = selection->GetRangeAt(0);
+    MOZ_ASSERT(firstRange);
+    firstRange.forget(aFirstSelectedRange);
   }
 
-  // Setup for next cell
-  mSelectedCellIndex = 1;
-
   return NS_OK;
 }
 
+already_AddRefed<Element>
+HTMLEditor::GetFirstSelectedTableCellElement(Selection& aSelection,
+                                             ErrorResult& aRv) const
+{
+  MOZ_ASSERT(!aRv.Failed());
+
+  nsRange* firstRange = aSelection.GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    // XXX Why don't we treat "not found" in this case?
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  // XXX It must be unclear when this is reset...
+  mSelectedCellIndex = 0;
+
+  RefPtr<Element> selectedCell;
+  nsresult rv =
+    HTMLEditor::GetCellFromRange(firstRange, getter_AddRefs(selectedCell));
+  if (NS_FAILED(rv)) {
+    // This case occurs only when Selection is in a text node in normal cases.
+    return nullptr;
+  }
+  if (!selectedCell) {
+    // This case means that the range does not select only a cell.
+    // E.g., selects non-table cell element, selects two or more cells, or
+    //       does not select any cell element.
+    return nullptr;
+  }
+
+  // Setup for GetNextSelectedCell()
+  // XXX Oh, increment it now?  Rather than when GetNextSelectedCell() is
+  //     called?
+  mSelectedCellIndex = 1;
+
+  return selectedCell.forget();
+}
+
 NS_IMETHODIMP
 HTMLEditor::GetNextSelectedCell(nsRange** aRange,
                                 Element** aCell)
 {
   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
   *aCell = nullptr;
   if (aRange) {
     *aRange = nullptr;
@@ -3323,17 +3382,17 @@ HTMLEditor::GetNextSelectedCell(nsRange*
   }
 
   // Scan through ranges to find next valid selected cell
   RefPtr<nsRange> range;
   for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++) {
     range = selection->GetRangeAt(mSelectedCellIndex);
     NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
 
-    nsresult rv = GetCellFromRange(range, aCell);
+    nsresult rv = HTMLEditor::GetCellFromRange(range, aCell);
     // Failure here means the range doesn't contain a cell
     NS_ENSURE_SUCCESS(rv, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
 
     // We found a selected cell
     if (*aCell) {
       break;
     }
 
@@ -3361,31 +3420,40 @@ HTMLEditor::GetFirstSelectedCellInTable(
   *aCell = nullptr;
   if (aRowIndex) {
     *aRowIndex = 0;
   }
   if (aColumnIndex) {
     *aColumnIndex = 0;
   }
 
-  RefPtr<Element> cell;
-  nsresult rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (NS_WARN_IF(!firstSelectedCellElement)) {
+    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  }
 
   // We don't want to cell.forget() here, because we use "cell" below.
-  *aCell = do_AddRef(cell).take();
+  firstSelectedCellElement.forget(aCell);
 
   if (!aRowIndex && !aColumnIndex) {
     return NS_OK;
   }
 
   // Also return the row and/or column if requested
-  ErrorResult error;
-  CellIndexes cellIndexes(*cell, error);
+  CellIndexes cellIndexes(*firstSelectedCellElement, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
   if (aRowIndex) {
     *aRowIndex = cellIndexes.mRow;
   }
   if (aColumnIndex) {
     *aColumnIndex = cellIndexes.mColumn;
@@ -3483,23 +3551,27 @@ HTMLEditor::GetSelectedOrParentTableElem
 {
   NS_ENSURE_ARG_POINTER(aTableElement);
   NS_ENSURE_ARG_POINTER(aSelectedCount);
   *aTableElement = nullptr;
   aTagName.Truncate();
   *aSelectedCount = 0;
 
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // Try to get the first selected cell
-  RefPtr<Element> tableOrCellElement;
-  nsresult rv = GetFirstSelectedCell(nullptr,
-                                     getter_AddRefs(tableOrCellElement));
-  NS_ENSURE_SUCCESS(rv, rv);
+  ErrorResult error;
+  RefPtr<Element> tableOrCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   if (tableOrCellElement) {
       // Each cell is in its own selection range,
       //  so count signals multiple-cell selection
       *aSelectedCount = selection->RangeCount();
       aTagName = NS_LITERAL_STRING("td");
   } else {
     nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
@@ -3545,104 +3617,114 @@ HTMLEditor::GetSelectedOrParentTableElem
 
 NS_IMETHODIMP
 HTMLEditor::GetSelectedCellsType(Element* aElement,
                                  uint32_t* aSelectionType)
 {
   NS_ENSURE_ARG_POINTER(aSelectionType);
   *aSelectionType = 0;
 
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Be sure we have a table element
   //  (if aElement is null, this uses selection's anchor node)
   RefPtr<Element> table;
   if (aElement) {
     table = GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aElement);
     if (NS_WARN_IF(!table)) {
       return NS_ERROR_FAILURE;
     }
   } else {
-    RefPtr<Selection> selection = GetSelection();
-    if (NS_WARN_IF(!selection)) {
-      return NS_ERROR_FAILURE;
-    }
     table =
       GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
     if (NS_WARN_IF(!table)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   ErrorResult error;
   TableSize tableSize(*this, *table, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   // Traverse all selected cells
-  RefPtr<Element> selectedCell;
-  nsresult rv = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
+  RefPtr<Element> selectedCell =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (!selectedCell) {
     return NS_OK;
   }
 
   // We have at least one selected cell, so set return value
   *aSelectionType = static_cast<uint32_t>(TableSelection::Cell);
 
   // Store indexes of each row/col to avoid duplication of searches
   nsTArray<int32_t> indexArray;
 
   bool allCellsInRowAreSelected = false;
   bool allCellsInColAreSelected = false;
-  while (NS_SUCCEEDED(rv) && selectedCell) {
+  while (selectedCell) {
     CellIndexes selectedCellIndexes(*selectedCell, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     if (!indexArray.Contains(selectedCellIndexes.mColumn)) {
       indexArray.AppendElement(selectedCellIndexes.mColumn);
       allCellsInRowAreSelected =
         AllCellsInRowSelected(table, selectedCellIndexes.mRow,
                               tableSize.mColumnCount);
       // We're done as soon as we fail for any row
       if (!allCellsInRowAreSelected) {
         break;
       }
     }
-    rv = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
+    DebugOnly<nsresult> rv =
+      GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+      "Failed to get next selected table cell element");
   }
 
   if (allCellsInRowAreSelected) {
     *aSelectionType = static_cast<uint32_t>(TableSelection::Row);
     return NS_OK;
   }
   // Test for columns
 
   // Empty the indexArray
   indexArray.Clear();
 
   // Start at first cell again
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell));
-  while (NS_SUCCEEDED(rv) && selectedCell) {
+  IgnoredErrorResult ignoredError;
+  selectedCell = GetFirstSelectedTableCellElement(*selection, ignoredError);
+  while (selectedCell) {
     CellIndexes selectedCellIndexes(*selectedCell, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
     if (!indexArray.Contains(selectedCellIndexes.mRow)) {
       indexArray.AppendElement(selectedCellIndexes.mColumn);
       allCellsInColAreSelected =
         AllCellsInColumnSelected(table, selectedCellIndexes.mColumn,
                                  tableSize.mRowCount);
       // We're done as soon as we fail for any column
       if (!allCellsInRowAreSelected) {
         break;
       }
     }
-    rv = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
+    DebugOnly<nsresult> rv =
+      GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+      "Failed to get next selected table cell element");
   }
   if (allCellsInColAreSelected) {
     *aSelectionType = static_cast<uint32_t>(TableSelection::Column);
   }
 
   return NS_OK;
 }
 
--- a/editor/nsITableEditor.idl
+++ b/editor/nsITableEditor.idl
@@ -298,29 +298,32 @@ interface nsITableEditor : nsISupports
     *                              and in each row, all cells selected
     *                              Note: This is the value if all rows
     *                              (thus all cells) are selected
     *     TableSelection::Column   All cells are in 1 or more columns
     *                              and in each column, all cells are selected
     */
   uint32_t getSelectedCellsType(in Element aElement);
 
-  /** Get first selected element from first selection range.
-    *   (If multiple cells were selected this is the first in the order they were selected)
-    * Assumes cell-selection model where each cell
-    * is in a separate range (selection parent node is table row)
-    * @param aCell     [OUT] Selected cell or null if ranges don't contain
-    *                  cell selections
-    * @param aRange    [OUT] Optional: if not null, return the selection range
-    *                     associated with the cell
-    * Returns the DOM cell element
-    *   (in C++: returns NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found
-    *    passes NS_SUCCEEDED macro)
-    */
-  Element getFirstSelectedCell(out Range aRange);
+  /**
+   * getFirstSelectedCell() returns a <td> or <th> element if first range of
+   * Selection selects only one table cell element (i.e., startContainer and
+   * endContainer are same <tr> element and startOffset + 1 equals endOffset).
+   * If first range of Selection does not select a table cell element, this
+   * returns null.  However, if Selection has no range, this throws an
+   * exception.
+   *
+   * @param aFirstRangeOfSelection [OUT] Returns the first range of Selection
+   *                               only when this returns a <td> or <th>
+   *                               element.  Otherwise, returns null.
+   * @return                       A <td> or <th> element if first range of
+   *                               Selection selects only one table cell
+   *                               element.
+   */
+  Element getFirstSelectedCell(out Range aFirstRangeOfSelection);
 
   /** Get first selected element in the table
     *   This is the upper-left-most selected cell in table,
     *   ignoring the order that the user selected them (order in the selection ranges)
     * Assumes cell-selection model where each cell
     * is in a separate range (selection parent node is table row)
     * @param aCell       Selected cell or null if ranges don't contain
     *                    cell selections