mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
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:
parent
fd5c8abedb
commit
db32271cec
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user