Bug 596501 TextEditRules::WillOutputText() should handle itself if it can return only the text of first text node r?m_kato
TextEditor::OutputToString() uses DocumentEncoder if TextEditRules::WillOutputText() doesn't handle it. However, TextEditRules::WillOutputText() doesn't handle it even if it's really simple case of <input type="text"> and <textarea>.
This patch makes TextEditRules::WillOutputText() handle it if DOM tree in the editor root element is expected and there is no special flag which requires complicated handling.
MozReview-Commit-ID: 3HvdTAWRpw0
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -20,16 +20,17 @@
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
+#include "nsIDocumentEncoder.h"
#include "nsIDOMDocument.h"
#include "nsIDOMNode.h"
#include "nsIDOMNodeFilter.h"
#include "nsNameSpaceManager.h"
#include "nsINode.h"
#include "nsIPlaintextEditor.h"
#include "nsISupportsBase.h"
#include "nsLiteralString.h"
@@ -291,17 +292,17 @@ TextEditRules::WillDoAction(Selection* a
case EditAction::redo:
return WillRedo(aSelection, aCancel, aHandled);
case EditAction::setTextProperty:
return WillSetTextProperty(aSelection, aCancel, aHandled);
case EditAction::removeTextProperty:
return WillRemoveTextProperty(aSelection, aCancel, aHandled);
case EditAction::outputText:
return WillOutputText(aSelection, info->outputFormat, info->outString,
- aCancel, aHandled);
+ info->flags, aCancel, aHandled);
case EditAction::insertElement:
// i had thought this would be html rules only. but we put pre elements
// into plaintext mail when doing quoting for reply! doh!
WillInsert(*aSelection, aCancel);
return NS_OK;
default:
return NS_ERROR_FAILURE;
}
@@ -1181,39 +1182,124 @@ TextEditRules::DidRedo(Selection* aSelec
}
return NS_OK;
}
nsresult
TextEditRules::WillOutputText(Selection* aSelection,
const nsAString* aOutputFormat,
nsAString* aOutString,
+ uint32_t aFlags,
bool* aCancel,
bool* aHandled)
{
// null selection ok
- if (!aOutString || !aOutputFormat || !aCancel || !aHandled) {
+ if (NS_WARN_IF(!aOutString) || NS_WARN_IF(!aOutputFormat) ||
+ NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
return NS_ERROR_NULL_POINTER;
}
// initialize out param
*aCancel = false;
*aHandled = false;
- if (aOutputFormat->LowerCaseEqualsLiteral("text/plain")) {
- // Only use these rules for plain text output.
- if (IsPasswordEditor()) {
- *aOutString = mPasswordText;
- *aHandled = true;
- } else if (mBogusNode) {
- // This means there's no content, so output null string.
- aOutString->Truncate();
- *aHandled = true;
- }
+ if (!aOutputFormat->LowerCaseEqualsLiteral("text/plain")) {
+ return NS_OK;
+ }
+
+ // XXX Looks like that even if it's password field, we need to use the
+ // expensive path if the caller requests some complicated handling.
+ // However, changing the behavior for password field might cause
+ // security issue. So, be careful when you touch here.
+ if (IsPasswordEditor()) {
+ *aOutString = mPasswordText;
+ *aHandled = true;
+ return NS_OK;
+ }
+
+ // If there is a bogus node, there's no content. So output empty string.
+ if (mBogusNode) {
+ aOutString->Truncate();
+ *aHandled = true;
+ return NS_OK;
+ }
+
+ // If it's necessary to check selection range or the editor wraps hard,
+ // we need some complicated handling. In such case, we need to use the
+ // expensive path.
+ // XXX Anything else what we cannot return plain text simply?
+ if (aFlags & nsIDocumentEncoder::OutputSelectionOnly ||
+ aFlags & nsIDocumentEncoder::OutputWrap) {
+ return NS_OK;
+ }
+
+ // If it's neither <input type="text"> nor <textarea>, e.g., an HTML editor
+ // which is in plaintext mode (e.g., plaintext email composer on Thunderbird),
+ // it should be handled by the expensive path.
+ if (NS_WARN_IF(!mTextEditor) || mTextEditor->AsHTMLEditor()) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> root = mTextEditor->GetRoot();
+ if (!root) { // Don't warn it, this is possible, e.g., 997805.html
+ aOutString->Truncate();
+ *aHandled = true;
+ return NS_OK;
}
+
+ nsIContent* firstChild = root->GetFirstChild();
+ if (!firstChild) {
+ aOutString->Truncate();
+ *aHandled = true;
+ return NS_OK;
+ }
+
+ // If it's an <input type="text"> element, the DOM tree should be:
+ // <div class="anonymous-div">
+ // #text
+ // </div>
+ //
+ // If it's a <textarea> element, the DOM tree should be:
+ // <div class="anonymous-div">
+ // #text (if there is)
+ // <br type="_moz">
+ // <scrollbar orient="horizontal">
+ // ...
+ // </div>
+
+ Text* text = firstChild->GetAsText();
+ nsIContent* firstChildExceptText =
+ text ? firstChild->GetNextSibling() : firstChild;
+ // If the DOM tree is unexpected, fall back to the expensive path.
+ bool isInput = IsSingleLineEditor();
+ bool isTextarea = !isInput;
+ if (NS_WARN_IF(isInput && firstChildExceptText) ||
+ NS_WARN_IF(isTextarea && !firstChildExceptText) ||
+ NS_WARN_IF(isTextarea &&
+ !TextEditUtils::IsMozBR(firstChildExceptText) &&
+ !firstChildExceptText->IsXULElement(nsGkAtoms::scrollbar))) {
+ return NS_OK;
+ }
+
+ // If there is no text node in the expected DOM tree, we can say that it's
+ // just empty.
+ if (!text) {
+ aOutString->Truncate();
+ *aHandled = true;
+ return NS_OK;
+ }
+
+ // Otherwise, the text is the value.
+ nsresult rv = text->GetData(*aOutString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Fall back to the expensive path if it fails.
+ return NS_OK;
+ }
+
+ *aHandled = true;
return NS_OK;
}
nsresult
TextEditRules::DidOutputText(Selection* aSelection,
nsresult aResult)
{
return NS_OK;
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -166,16 +166,17 @@ protected:
* @param aInFormat The format requested for the output, a MIME type.
* @param aOutText The string to use for output, if aCancel is set to true.
* @param aOutCancel If set to true, the caller should cancel the operation
* and use aOutText as the result.
*/
nsresult WillOutputText(Selection* aSelection,
const nsAString* aInFormat,
nsAString* aOutText,
+ uint32_t aFlags,
bool* aOutCancel,
bool* aHandled);
nsresult DidOutputText(Selection* aSelection, nsresult aResult);
/**
* Check for and replace a redundant trailing break.
*/
@@ -256,40 +257,46 @@ protected:
nsCOMPtr<nsITimer> mTimer;
uint32_t mLastStart;
uint32_t mLastLength;
// friends
friend class AutoLockRulesSniffing;
};
+// TODO: This class (almost struct, though) is ugly and its size isn't
+// optimized. Should be refined later.
class TextRulesInfo final : public RulesInfo
{
public:
explicit TextRulesInfo(EditAction aAction)
: RulesInfo(aAction)
, inString(nullptr)
, outString(nullptr)
, outputFormat(nullptr)
, maxLength(-1)
+ , flags(0)
, collapsedAction(nsIEditor::eNext)
, stripWrappers(nsIEditor::eStrip)
, bOrdered(false)
, entireList(false)
, bulletType(nullptr)
, alignType(nullptr)
, blockType(nullptr)
{}
// EditAction::insertText / EditAction::insertIMEText
const nsAString* inString;
nsAString* outString;
const nsAString* outputFormat;
int32_t maxLength;
+ // EditAction::outputText
+ uint32_t flags;
+
// EditAction::deleteSelection
nsIEditor::EDirection collapsedAction;
nsIEditor::EStripWrappers stripWrappers;
// EditAction::removeList
bool bOrdered;
// EditAction::makeList
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1308,16 +1308,17 @@ TextEditor::OutputToString(const nsAStri
nsAString& aOutputString)
{
// Protect the edit rules object from dying
nsCOMPtr<nsIEditRules> rules(mRules);
nsString resultString;
TextRulesInfo ruleInfo(EditAction::outputText);
ruleInfo.outString = &resultString;
+ ruleInfo.flags = aFlags;
// XXX Struct should store a nsAReadable*
nsAutoString str(aFormatType);
ruleInfo.outputFormat = &str;
bool cancel, handled;
nsresult rv = rules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled);
if (cancel || NS_FAILED(rv)) {
return rv;
}