nsresult TextEditRules::CollapseSelectionToTrailingBRIfNeeded(Selection* aSelection) { // we only need to execute the stuff below if we are a plaintext editor. // html editors have a different mechanism for putting in mozBR's // (because there are a bunch more places you have to worry about it in html) if (!IsPlaintextEditor()) { return NS_OK; } NS_ENSURE_STATE(mTextEditor); // If there is no selection ranges, we should set to the end of the editor. // This is usually performed in TextEditRules::Init(), however, if the // editor is reframed, this may be called by AfterEdit(). if (!aSelection->RangeCount()) { mTextEditor->CollapseSelectionToEnd(aSelection); } // if we are at the end of the textarea, we need to set the // selection to stick to the mozBR at the end of the textarea. int32_t selOffset; nsCOMPtr<nsINode> selNode; nsresult rv = EditorBase::GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!EditorBase::IsTextNode(selNode)) { return NS_OK; // Nothing to do if we're not at a text node. } // nothing to do if we're not at the end of the text node if (selOffset != static_cast<int32_t>(selNode->Length())) { return NS_OK; } NS_ENSURE_STATE(mTextEditor); nsINode* root = mTextEditor->GetRoot(); if (NS_WARN_IF(!root)) { return NS_ERROR_NULL_POINTER; } nsINode* parentNode = selNode->GetParentNode(); if (parentNode != root) { return NS_OK; } nsINode* nextNode = selNode->GetNextSibling(); if (nextNode && TextEditUtils::IsMozBR(nextNode)) { int32_t offsetInParent = EditorBase::GetChildOffset(selNode, parentNode); rv = aSelection->Collapse(parentNode, offsetInParent + 1); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; }
nsresult nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // XXX: should probably return a success value other than NS_OK that means "not allowed" if (IsPlaintextEditor()) { *aCancel = PR_TRUE; } return NS_OK; }
nsresult nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection) { // we only need to execute the stuff below if we are a plaintext editor. // html editors have a different mechanism for putting in mozBR's // (because there are a bunch more places you have to worry about it in html) if (!IsPlaintextEditor()) { return NS_OK; } // if we are at the end of the textarea, we need to set the // selection to stick to the mozBR at the end of the textarea. PRInt32 selOffset; nsCOMPtr<nsIDOMNode> selNode; nsresult res; res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode); if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node PRUint32 length; res = nodeAsText->GetLength(&length); NS_ENSURE_SUCCESS(res, res); // nothing to do if we're not at the end of the text node if (selOffset != PRInt32(length)) return NS_OK; nsCOMPtr<nsIDOMNode> parentNode; PRInt32 parentOffset; res = nsEditor::GetNodeLocation(selNode, address_of(parentNode), &parentOffset); NS_ENSURE_SUCCESS(res, res); nsIDOMElement *rootElem = mEditor->GetRoot(); nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem); NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); if (parentNode != root) return NS_OK; nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode, parentOffset + 1); if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) { res = aSelection->Collapse(parentNode, parentOffset + 1); NS_ENSURE_SUCCESS(res, res); } return res; }
nsresult TextEditRules::WillSetTextProperty(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // XXX: should probably return a success value other than NS_OK that means "not allowed" if (IsPlaintextEditor()) { *aCancel = true; } return NS_OK; }
nsresult nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) { // we only need to execute the stuff below if we are a plaintext editor. // html editors have a different mechanism for putting in mozBR's // (because there are a bunch more places you have to worry about it in html) if (!IsPlaintextEditor()) { return NS_OK; } // if we are at the end of the document, we need to insert // a special mozBR following the normal br, and then set the // selection to stick to the mozBR. PRInt32 selOffset; nsCOMPtr<nsIDOMNode> selNode; nsresult res; res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // confirm we are at end of document if (selOffset == 0) return NS_OK; // can't be after a br if we are at offset 0 nsIDOMElement *rootElem = mEditor->GetRoot(); nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem); NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); if (selNode != root) return NS_OK; // must be inside text node or somewhere other than end of root nsCOMPtr<nsIDOMNode> temp = mEditor->GetChildAt(selNode, selOffset); if (temp) return NS_OK; // can't be at end if there is a node after us. nsCOMPtr<nsIDOMNode> nearNode = mEditor->GetChildAt(selNode, selOffset-1); if (nearNode && nsTextEditUtils::IsBreak(nearNode) && !nsTextEditUtils::IsMozBR(nearNode)) { nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection)); // need to insert special moz BR. Why? Because if we don't // the user will see no new line for the break. Also, things // like table cells won't grow in height. nsCOMPtr<nsIDOMNode> brNode; res = CreateMozBR(selNode, selOffset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); res = nsEditor::GetNodeLocation(brNode, address_of(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); selPrivate->SetInterlinePosition(PR_TRUE); res = aSelection->Collapse(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); } return res; }
NS_IMETHODIMP nsTextEditRules::Init(nsPlaintextEditor *aEditor) { if (!aEditor) { return NS_ERROR_NULL_POINTER; } mEditor = aEditor; // we hold a non-refcounted reference back to our editor nsCOMPtr<nsISelection> selection; mEditor->GetSelection(getter_AddRefs(selection)); NS_ASSERTION(selection, "editor cannot get selection"); // Put in a magic br if needed. This method handles null selection, // which should never happen anyway nsresult res = CreateBogusNodeIfNeeded(selection); NS_ENSURE_SUCCESS(res, res); // If the selection hasn't been set up yet, set it up collapsed to the end of // our editable content. PRInt32 rangeCount; res = selection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(res, res); if (!rangeCount) { res = mEditor->EndOfDocument(); NS_ENSURE_SUCCESS(res, res); } if (IsPlaintextEditor()) { // ensure trailing br node res = CreateTrailingBRIfNeeded(); NS_ENSURE_SUCCESS(res, res); } PRBool deleteBidiImmediately = PR_FALSE; nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &res); if (NS_SUCCEEDED(res)) prefBranch->GetBoolPref("bidi.edit.delete_immediately", &deleteBidiImmediately); mDeleteBidiImmediately = deleteBidiImmediately; return res; }
NS_IMETHODIMP TextEditRules::Init(TextEditor* aTextEditor) { if (!aTextEditor) { return NS_ERROR_NULL_POINTER; } InitFields(); // We hold a non-refcounted reference back to our editor. mTextEditor = aTextEditor; RefPtr<Selection> selection = mTextEditor->GetSelection(); NS_WARNING_ASSERTION(selection, "editor cannot get selection"); // Put in a magic br if needed. This method handles null selection, // which should never happen anyway nsresult rv = CreateBogusNodeIfNeeded(selection); NS_ENSURE_SUCCESS(rv, rv); // If the selection hasn't been set up yet, set it up collapsed to the end of // our editable content. int32_t rangeCount; rv = selection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(rv, rv); if (!rangeCount) { rv = mTextEditor->CollapseSelectionToEnd(selection); NS_ENSURE_SUCCESS(rv, rv); } if (IsPlaintextEditor()) { // ensure trailing br node rv = CreateTrailingBRIfNeeded(); NS_ENSURE_SUCCESS(rv, rv); } mDeleteBidiImmediately = Preferences::GetBool("bidi.edit.delete_immediately", false); return NS_OK; }
nsresult TextEditRules::WillSetText(Selection& aSelection, bool* aCancel, bool* aHandled, const nsAString* aString, int32_t aMaxLength) { MOZ_ASSERT(aCancel); MOZ_ASSERT(aHandled); MOZ_ASSERT(aString); CANCEL_OPERATION_IF_READONLY_OR_DISABLED *aHandled = false; *aCancel = false; if (NS_WARN_IF(!mTextEditor)) { return NS_ERROR_FAILURE; } RefPtr<TextEditor> textEditor = mTextEditor; if (!IsPlaintextEditor() || textEditor->IsIMEComposing() || aMaxLength != -1) { // SetTextImpl only supports plain text editor without IME. return NS_OK; } if (IsPasswordEditor() && LookAndFeel::GetEchoPassword() && !DontEchoPassword()) { // Echo password timer will implement on InsertText. return NS_OK; } WillInsert(aSelection, aCancel); // we want to ignore result of WillInsert() *aCancel = false; RefPtr<Element> rootElement = textEditor->GetRoot(); uint32_t count = rootElement->GetChildCount(); // handles only when there is only one node and it's a text node, or empty. if (count > 1) { return NS_OK; } nsAutoString tString(*aString); if (IsPasswordEditor()) { mPasswordText.Assign(tString); FillBufWithPWChars(&tString, tString.Length()); } else if (IsSingleLineEditor()) { HandleNewLines(tString, textEditor->mNewlineHandling); } if (!count) { if (tString.IsEmpty()) { *aHandled = true; return NS_OK; } RefPtr<nsIDocument> doc = textEditor->GetDocument(); if (NS_WARN_IF(!doc)) { return NS_OK; } RefPtr<nsTextNode> newNode = EditorBase::CreateTextNode(*doc, tString); if (NS_WARN_IF(!newNode)) { return NS_OK; } nsresult rv = textEditor->InsertNode(*newNode, *rootElement, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aHandled = true; ASSERT_PASSWORD_LENGTHS_EQUAL(); return NS_OK; } nsINode* curNode = rootElement->GetFirstChild(); if (NS_WARN_IF(!EditorBase::IsTextNode(curNode))) { return NS_OK; } // don't change my selection in subtransactions AutoTransactionsConserveSelection dontChangeMySelection(textEditor); // Even if empty text, we don't remove text node and set empty text // for performance nsresult rv = textEditor->SetTextImpl(aSelection, tString, *curNode->GetAsText()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aHandled = true; ASSERT_PASSWORD_LENGTHS_EQUAL(); return NS_OK; }
nsresult TextEditRules::TruncateInsertionIfNeeded(Selection* aSelection, const nsAString* aInString, nsAString* aOutString, int32_t aMaxLength, bool* aTruncated) { if (!aSelection || !aInString || !aOutString) { return NS_ERROR_NULL_POINTER; } if (!aOutString->Assign(*aInString, mozilla::fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (aTruncated) { *aTruncated = false; } NS_ENSURE_STATE(mTextEditor); if (-1 != aMaxLength && IsPlaintextEditor() && !mTextEditor->IsIMEComposing()) { // Get the current text length. // Get the length of inString. // Get the length of the selection. // If selection is collapsed, it is length 0. // Subtract the length of the selection from the len(doc) // since we'll delete the selection on insert. // This is resultingDocLength. // Get old length of IME composing string // which will be replaced by new one. // If (resultingDocLength) is at or over max, cancel the insert // If (resultingDocLength) + (length of input) > max, // set aOutString to subset of inString so length = max int32_t docLength; nsresult rv = mTextEditor->GetTextLength(&docLength); if (NS_FAILED(rv)) { return rv; } uint32_t start, end; nsContentUtils::GetSelectionInTextControl(aSelection, mTextEditor->GetRoot(), start, end); TextComposition* composition = mTextEditor->GetComposition(); uint32_t oldCompStrLength = composition ? composition->String().Length() : 0; const uint32_t selectionLength = end - start; const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength; if (resultingDocLength >= aMaxLength) { // This call is guaranteed to reduce the capacity of the string, so it // cannot cause an OOM. aOutString->Truncate(); if (aTruncated) { *aTruncated = true; } } else { int32_t oldLength = aOutString->Length(); if (oldLength + resultingDocLength > aMaxLength) { int32_t newLength = aMaxLength - resultingDocLength; MOZ_ASSERT(newLength > 0); char16_t newLastChar = aOutString->CharAt(newLength - 1); char16_t removingFirstChar = aOutString->CharAt(newLength); // Don't separate the string between a surrogate pair. if (NS_IS_HIGH_SURROGATE(newLastChar) && NS_IS_LOW_SURROGATE(removingFirstChar)) { newLength--; } // XXX What should we do if we're removing IVS and its preceding // character won't be removed? // This call is guaranteed to reduce the capacity of the string, so it // cannot cause an OOM. aOutString->Truncate(newLength); if (aTruncated) { *aTruncated = true; } } } } return NS_OK; }
nsresult TextEditRules::CollapseSelectionToTrailingBRIfNeeded(Selection* aSelection) { // we only need to execute the stuff below if we are a plaintext editor. // html editors have a different mechanism for putting in mozBR's // (because there are a bunch more places you have to worry about it in html) if (!IsPlaintextEditor()) { return NS_OK; } NS_ENSURE_STATE(mTextEditor); // If there is no selection ranges, we should set to the end of the editor. // This is usually performed in TextEditRules::Init(), however, if the // editor is reframed, this may be called by AfterEdit(). if (!aSelection->RangeCount()) { mTextEditor->EndOfDocument(); } // if we are at the end of the textarea, we need to set the // selection to stick to the mozBR at the end of the textarea. int32_t selOffset; nsCOMPtr<nsIDOMNode> selNode; nsresult rv = EditorBase::GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode); if (!nodeAsText) { return NS_OK; // Nothing to do if we're not at a text node. } uint32_t length; rv = nodeAsText->GetLength(&length); NS_ENSURE_SUCCESS(rv, rv); // nothing to do if we're not at the end of the text node if (selOffset != int32_t(length)) { return NS_OK; } int32_t parentOffset; nsCOMPtr<nsIDOMNode> parentNode = EditorBase::GetNodeLocation(selNode, &parentOffset); NS_ENSURE_STATE(mTextEditor); nsCOMPtr<nsIDOMNode> root = do_QueryInterface(mTextEditor->GetRoot()); NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); if (parentNode != root) { return NS_OK; } nsCOMPtr<nsIDOMNode> nextNode = mTextEditor->GetChildAt(parentNode, parentOffset + 1); if (nextNode && TextEditUtils::IsMozBR(nextNode)) { rv = aSelection->Collapse(parentNode, parentOffset + 1); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; }
nsresult nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection, const nsAString *aInString, nsAString *aOutString, PRInt32 aMaxLength, PRBool *aTruncated) { if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;} nsresult res = NS_OK; *aOutString = *aInString; if (aTruncated) { *aTruncated = PR_FALSE; } if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() ) { // Get the current text length. // Get the length of inString. // Get the length of the selection. // If selection is collapsed, it is length 0. // Subtract the length of the selection from the len(doc) // since we'll delete the selection on insert. // This is resultingDocLength. // Get old length of IME composing string // which will be replaced by new one. // If (resultingDocLength) is at or over max, cancel the insert // If (resultingDocLength) + (length of input) > max, // set aOutString to subset of inString so length = max PRInt32 docLength; res = mEditor->GetTextLength(&docLength); if (NS_FAILED(res)) { return res; } PRUint32 start, end; res = mEditor->GetTextSelectionOffsets(aSelection, start, end); if (NS_FAILED(res)) { return res; } PRInt32 oldCompStrLength; res = mEditor->GetIMEBufferLength(&oldCompStrLength); if (NS_FAILED(res)) { return res; } const PRInt32 selectionLength = end - start; const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength; if (resultingDocLength >= aMaxLength) { aOutString->Truncate(); if (aTruncated) { *aTruncated = PR_TRUE; } } else { PRInt32 inCount = aOutString->Length(); if (inCount + resultingDocLength > aMaxLength) { aOutString->Truncate(aMaxLength - resultingDocLength); if (aTruncated) { *aTruncated = PR_TRUE; } } } } return res; }
NS_IMETHODIMP nsTextEditRules::Init(nsPlaintextEditor *aEditor) { if (!aEditor) { return NS_ERROR_NULL_POINTER; } mEditor = aEditor; // we hold a non-refcounted reference back to our editor nsCOMPtr<nsISelection> selection; mEditor->GetSelection(getter_AddRefs(selection)); NS_ASSERTION(selection, "editor cannot get selection"); // Cache our body node, if available. nsIDOMNode *body = mEditor->GetRoot(); // Put in a magic br if needed. This method handles null selection, // which should never happen anyway nsresult res = CreateBogusNodeIfNeeded(selection); NS_ENSURE_SUCCESS(res, res); // If the selection hasn't been set up yet, set it up collapsed to the end of // our editable content. PRInt32 rangeCount; res = selection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(res, res); if (!rangeCount) { res = mEditor->EndOfDocument(); NS_ENSURE_SUCCESS(res, res); } if (IsPlaintextEditor()) { // ensure trailing br node res = CreateTrailingBRIfNeeded(); NS_ENSURE_SUCCESS(res, res); } if (body) { // create a range that is the entire body contents nsCOMPtr<nsIDOMRange> wholeDoc = do_CreateInstance("@mozilla.org/content/range;1"); NS_ENSURE_TRUE(wholeDoc, NS_ERROR_NULL_POINTER); wholeDoc->SetStart(body,0); nsCOMPtr<nsIDOMNodeList> list; res = body->GetChildNodes(getter_AddRefs(list)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(list, NS_ERROR_FAILURE); PRUint32 listCount; res = list->GetLength(&listCount); NS_ENSURE_SUCCESS(res, res); res = wholeDoc->SetEnd(body, listCount); NS_ENSURE_SUCCESS(res, res); // replace newlines in that range with breaks res = ReplaceNewlines(wholeDoc); } PRBool deleteBidiImmediately = PR_FALSE; nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &res); if (NS_SUCCEEDED(res)) prefBranch->GetBoolPref("bidi.edit.delete_immediately", &deleteBidiImmediately); mDeleteBidiImmediately = deleteBidiImmediately; return res; }