Bug 1540029 - part 2: Get rid of TextEditRules::DocumentIsEmpty() and HTMLEditRules::DocumentIsEmpty() r=m_kato

`TextEditRules::DocumentIsEmpty()` is a wrapper of `TextEditor::IsEmpty()` so
that we can get rid of it simply.

`HTMLEditRules::DocumentIsEmpty()` needs to change.  It's oddly checks only
`EditorBase::mPaddingBRElementForEmptyEditor` is `nullptr` or not.  However,
the editor may be completely empty.  And the result may be different from
`TextEditor::IsEmpty()` which is not overridden by `HTMLEditor`.  For partially
solving this issue, this patch makes `HTMLEditor` overrides `IsEmpty()` and
optimizes `TextEditor::IsEmpty()`.

With this change, the caller of `HTMLEditRules::DocumentIsEmpty()` may behave
differently in the only caller, `HTMLEditor::SelectEntireDocument()`.  And
unfortunately, its root called from `SelectAllCommand::DoCommand()`.  However,
it does just collapse `Selection` into the root element (`<body>` or
`Document.documentElement`) if it returns `true`.  Therefore, this change
must be safe since anyway `SelectionRefPtr()->SelectAllChildren()` with
the root element is exactly same as `SelectionRefPtr()->Collapse()` with
the empty root element if it's truly empty.

Differential Revision: https://phabricator.services.mozilla.com/D45784

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-09-17 06:58:06 +00:00
parent fd5c8abedb
commit db32271cec
11 changed files with 80 additions and 110 deletions

View File

@ -2510,13 +2510,11 @@ bool nsTextEditorState::SetValue(const nsAString& aValue,
}
bool nsTextEditorState::HasNonEmptyValue() {
// If the frame for editor is alive, we can compute it with mTextEditor.
// Otherwise, we need to check cached value via GetValue().
if (mTextEditor && mBoundFrame && mEditorInitialized &&
!mIsCommittingComposition) {
bool empty;
nsresult rv = mTextEditor->IsEmpty(&empty);
if (NS_SUCCEEDED(rv)) {
return !empty;
}
return !mTextEditor->IsEmpty();
}
nsAutoString value;

View File

@ -1699,15 +1699,6 @@ class EditorBase : public nsIEditor,
EditorDOMPoint JoinNodesDeepWithTransaction(nsIContent& aLeftNode,
nsIContent& aRightNode);
/**
* HasPaddingBRElementForEmptyEditor() returns true if there is a padding
* <br> element for empty editor. When this returns true, it means that
* we're empty.
*/
bool HasPaddingBRElementForEmptyEditor() const {
return !!mPaddingBRElementForEmptyEditor;
}
/**
* EnsureNoPaddingBRElementForEmptyEditor() removes padding <br> element
* for empty editor if there is.

View File

@ -643,11 +643,7 @@ bool SelectAllCommand::IsCommandEnabled(Command aCommand,
}
// You can select all if there is an editor which is non-empty
bool isEmpty = false;
if (NS_WARN_IF(NS_FAILED(aTextEditor->IsEmpty(&isEmpty)))) {
return false;
}
return !isEmpty;
return !aTextEditor->IsEmpty();
}
nsresult SelectAllCommand::DoCommand(Command aCommand, TextEditor& aTextEditor,

View File

@ -742,12 +742,6 @@ EditActionResult HTMLEditor::CanHandleHTMLEditSubAction() const {
return EditActionIgnored();
}
bool HTMLEditRules::DocumentIsEmpty() const {
// XXX This is wrong. Even if there is no padding <br> element for empty
// editor, the editor may be empty.
return HTMLEditorRef().HasPaddingBRElementForEmptyEditor();
}
nsresult HTMLEditRules::GetListState(bool* aMixed, bool* aOL, bool* aUL,
bool* aDL) {
NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
@ -2283,7 +2277,7 @@ EditActionResult HTMLEditor::HandleDeleteSelectionInternal(
// If there is only padding `<br>` element for empty editor, cancel the
// operation.
if (HasPaddingBRElementForEmptyEditor()) {
if (mPaddingBRElementForEmptyEditor) {
return EditActionCanceled();
}

View File

@ -60,7 +60,6 @@ class HTMLEditRules : public TextEditRules {
virtual nsresult DetachEditor() override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult BeforeEdit() override;
MOZ_CAN_RUN_SCRIPT virtual nsresult AfterEdit() override;
virtual bool DocumentIsEmpty() const override;
/**
* DocumentModified() is called when editor content is changed.

View File

@ -3459,22 +3459,21 @@ bool HTMLEditor::IsContainer(nsINode* aNode) const {
nsresult HTMLEditor::SelectEntireDocument() {
MOZ_ASSERT(IsEditActionDataAvailable());
if (!mRules) {
return NS_ERROR_NULL_POINTER;
}
RefPtr<Element> rootElement = GetRoot();
if (NS_WARN_IF(!rootElement)) {
if (!mInitSucceeded) {
return NS_ERROR_NOT_INITIALIZED;
}
// Protect the edit rules object from dying
RefPtr<TextEditRules> rules(mRules);
// XXX It's odd to select all of the document body if an contenteditable
// element has focus.
RefPtr<Element> bodyOrDocumentElement = GetRoot();
if (NS_WARN_IF(!bodyOrDocumentElement)) {
return NS_ERROR_NOT_INITIALIZED;
}
// If we're empty, don't select all children because that would select the
// padding <br> element for empty editor.
if (rules->DocumentIsEmpty()) {
nsresult rv = SelectionRefPtr()->Collapse(rootElement, 0);
if (IsEmpty()) {
nsresult rv = SelectionRefPtr()->Collapse(bodyOrDocumentElement, 0);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to move caret to start of the editor root element");
@ -3483,7 +3482,7 @@ nsresult HTMLEditor::SelectEntireDocument() {
// Otherwise, select all children.
ErrorResult error;
SelectionRefPtr()->SelectAllChildren(*rootElement, error);
SelectionRefPtr()->SelectAllChildren(*bodyOrDocumentElement, error);
NS_WARNING_ASSERTION(
!error.Failed(),
"Failed to select all children of the editor root element");
@ -3977,6 +3976,27 @@ bool HTMLEditor::IsVisibleTextNode(Text& aText) const {
&aText == nextVisibleNode;
}
bool HTMLEditor::IsEmpty() const {
if (mPaddingBRElementForEmptyEditor) {
return true;
}
// XXX Oddly, we check body or document element's state instead of
// active editing host. Must be a bug.
Element* bodyOrDocumentElement = GetRoot();
if (!bodyOrDocumentElement) {
return true;
}
for (nsIContent* childContent = bodyOrDocumentElement->GetFirstChild();
childContent; childContent = childContent->GetNextSibling()) {
if (!childContent->IsText() || childContent->Length()) {
return false;
}
}
return true;
}
/**
* IsEmptyNode() figures out if aNode is an empty node. A block can have
* children and still be considered empty, if the children are empty or

View File

@ -138,6 +138,14 @@ class HTMLEditor final : public TextEditor,
NS_IMETHOD BeginningOfDocument() override;
NS_IMETHOD SetFlags(uint32_t aFlags) override;
/**
* IsEmpty() checks whether the editor is empty. If editor has only padding
* <br> element for empty editor, returns true. If editor's root element has
* non-empty text nodes or other nodes like <br>, returns false even if there
* are only empty blocks.
*/
virtual bool IsEmpty() const override;
virtual bool CanPaste(int32_t aClipboardType) const override;
using EditorBase::CanPaste;

View File

@ -181,15 +181,6 @@ nsresult TextEditRules::AfterEdit() {
return NS_OK;
}
bool TextEditRules::DocumentIsEmpty() const {
bool retVal = false;
if (!mTextEditor || NS_FAILED(mTextEditor->IsEmpty(&retVal))) {
retVal = true;
}
return retVal;
}
EditActionResult TextEditor::InsertLineFeedCharacterAtSelection() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!AsHTMLEditor());
@ -725,7 +716,7 @@ EditActionResult TextEditor::HandleDeleteSelection(
// if there is only padding <br> element for empty editor, cancel the
// operation.
if (HasPaddingBRElementForEmptyEditor()) {
if (mPaddingBRElementForEmptyEditor) {
return EditActionCanceled();
}
EditActionResult result =
@ -801,7 +792,7 @@ EditActionResult TextEditor::ComputeValueFromTextNodeAndPaddingBRElement(
// If there is a padding <br> element, there's no content. So output empty
// string.
if (HasPaddingBRElementForEmptyEditor()) {
if (mPaddingBRElementForEmptyEditor) {
aValue.Truncate();
return EditActionHandled();
}

View File

@ -79,13 +79,6 @@ class TextEditRules {
virtual nsresult BeforeEdit();
MOZ_CAN_RUN_SCRIPT virtual nsresult AfterEdit();
/**
* Return false if the editor has non-empty text nodes or non-text
* nodes. Otherwise, i.e., there is no meaningful content,
* return true.
*/
virtual bool DocumentIsEmpty() const;
protected:
virtual ~TextEditRules() = default;

View File

@ -1059,7 +1059,7 @@ nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(mPlaceholderBatch);
if (NS_WARN_IF(!mRules)) {
if (NS_WARN_IF(!mInitSucceeded)) {
return NS_ERROR_NOT_INITIALIZED;
}
@ -1091,11 +1091,14 @@ nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
// XXX We should make ReplaceSelectionAsSubAction() take range. Then,
// we can saving the expensive cost of modifying `Selection` here.
nsresult rv;
if (mRules && mRules->DocumentIsEmpty()) {
if (IsEmpty()) {
rv = SelectionRefPtr()->Collapse(rootElement, 0);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Selection::Collapse() failed, but ignored");
} else {
// XXX Oh, we shouldn't select padding `<br>` element for empty last
// line here since we will need to recreate it in multiline
// text editor.
ErrorResult error;
SelectionRefPtr()->SelectAllChildren(*rootElement, error);
NS_WARNING_ASSERTION(
@ -1312,44 +1315,29 @@ already_AddRefed<Element> TextEditor::GetInputEventTargetElement() {
return target.forget();
}
nsresult TextEditor::IsEmpty(bool* aIsEmpty) const {
if (NS_WARN_IF(!mRules)) {
return NS_ERROR_NOT_INITIALIZED;
}
*aIsEmpty = true;
bool TextEditor::IsEmpty() const {
if (mPaddingBRElementForEmptyEditor) {
return NS_OK;
return true;
}
// Even if there is no padding <br> element for empty editor, we should be
// detected as empty editor if all the children are text nodes and these
// have no content.
Element* rootElement = GetRoot();
if (!rootElement) {
// XXX Why don't we return an error in such case??
return NS_OK;
Element* anonymousDivElement = GetRoot();
if (!anonymousDivElement) {
return true; // Don't warn it, this is possible, e.g., 997805.html
}
for (nsIContent* child = rootElement->GetFirstChild(); child;
child = child->GetNextSibling()) {
if (!EditorBase::IsTextNode(child) ||
static_cast<nsTextNode*>(child)->TextDataLength()) {
*aIsEmpty = false;
return NS_OK;
}
}
return NS_OK;
// Only when there is non-empty text node, we are not empty.
return !anonymousDivElement->GetFirstChild() ||
!anonymousDivElement->GetFirstChild()->IsText() ||
!anonymousDivElement->GetFirstChild()->Length();
}
NS_IMETHODIMP
TextEditor::GetDocumentIsEmpty(bool* aDocumentIsEmpty) {
nsresult rv = IsEmpty(aDocumentIsEmpty);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(aDocumentIsEmpty);
*aDocumentIsEmpty = IsEmpty();
return NS_OK;
}
@ -1362,12 +1350,10 @@ TextEditor::GetTextLength(int32_t* aCount) {
// special-case for empty document, to account for the padding <br> element
// for empty editor.
bool isEmpty = false;
nsresult rv = IsEmpty(&isEmpty);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (isEmpty) {
// XXX This should be overridden by `HTMLEditor` and we should return the
// first text node's length from `TextEditor` instead. The following
// code is too expensive.
if (IsEmpty()) {
return NS_OK;
}
@ -2078,29 +2064,30 @@ void TextEditor::OnEndHandlingTopLevelEditSubAction() {
nsresult TextEditor::SelectEntireDocument() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!AsHTMLEditor());
if (!mRules) {
return NS_ERROR_NULL_POINTER;
}
Element* rootElement = GetRoot();
if (NS_WARN_IF(!rootElement)) {
if (!mInitSucceeded) {
return NS_ERROR_NOT_INITIALIZED;
}
// Protect the edit rules object from dying
RefPtr<TextEditRules> rules(mRules);
Element* anonymousDivElement = GetRoot();
if (NS_WARN_IF(!anonymousDivElement)) {
return NS_ERROR_NOT_INITIALIZED;
}
// If we're empty, don't select all children because that would select the
// padding <br> element for empty editor.
if (rules->DocumentIsEmpty()) {
nsresult rv = SelectionRefPtr()->Collapse(rootElement, 0);
if (IsEmpty()) {
nsresult rv = SelectionRefPtr()->Collapse(anonymousDivElement, 0);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to move caret to start of the editor root element");
return rv;
}
// XXX We just need to select all of first text node (if there is).
// Why do we do this kind of complicated things?
// Don't select the trailing BR node if we have one
nsCOMPtr<nsIContent> childNode;
nsresult rv = EditorBase::GetEndChildNode(*SelectionRefPtr(),
@ -2115,7 +2102,7 @@ nsresult TextEditor::SelectEntireDocument() {
if (childNode && EditorBase::IsPaddingBRElementForEmptyLastLine(*childNode)) {
ErrorResult error;
MOZ_KnownLive(SelectionRefPtr())
->SetStartAndEndInLimiter(RawRangeBoundary(rootElement, 0),
->SetStartAndEndInLimiter(RawRangeBoundary(anonymousDivElement, 0),
EditorRawDOMPoint(childNode), error);
NS_WARNING_ASSERTION(!error.Failed(),
"Failed to select all children of the editor root "
@ -2124,7 +2111,7 @@ nsresult TextEditor::SelectEntireDocument() {
}
ErrorResult error;
SelectionRefPtr()->SelectAllChildren(*rootElement, error);
SelectionRefPtr()->SelectAllChildren(*anonymousDivElement, error);
NS_WARNING_ASSERTION(
!error.Failed(),
"Failed to select all children of the editor root element");

View File

@ -171,14 +171,7 @@ class TextEditor : public EditorBase,
* <br> element for empty editor, returns true. If editor's root element has
* non-empty text nodes or other nodes like <br>, returns false.
*/
nsresult IsEmpty(bool* aIsEmpty) const;
bool IsEmpty() const {
bool isEmpty = false;
nsresult rv = IsEmpty(&isEmpty);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Checking whether the editor is empty failed");
return NS_SUCCEEDED(rv) && isEmpty;
}
virtual bool IsEmpty() const;
MOZ_CAN_RUN_SCRIPT
virtual nsresult HandleKeyPressEvent(