void IMEContentObserver::Destroy() { // If CreateTextStateManager failed, mRootContent will be null, // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)) if (mRootContent) { if (IMEStateManager::IsTestingIME() && mEditableNode) { nsIDocument* doc = mEditableNode->OwnerDoc(); (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusOut"), false, false))->RunDOMEventWhenSafe(); } mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); } // Even if there are some pending notification, it'll never notify the widget. mWidget = nullptr; if (mUpdatePreference.WantSelectionChange() && mSelection) { nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); if (selPrivate) { selPrivate->RemoveSelectionListener(this); } } mSelection = nullptr; if (mUpdatePreference.WantTextChange() && mRootContent) { mRootContent->RemoveMutationObserver(this); } if (mUpdatePreference.WantPositionChanged() && mDocShell) { mDocShell->RemoveWeakScrollObserver(this); mDocShell->RemoveWeakReflowObserver(this); } mRootContent = nullptr; mEditableNode = nullptr; mDocShell = nullptr; mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; }
void IMEContentObserver::UnregisterObservers() { if (!mIsObserving) { return; } mIsObserving = false; if (mEditor) { mEditor->RemoveEditorObserver(this); } if (mUpdatePreference.WantSelectionChange() && mSelection) { nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); if (selPrivate) { selPrivate->RemoveSelectionListener(this); } } if (mUpdatePreference.WantTextChange() && mRootContent) { mRootContent->RemoveMutationObserver(this); } if (mUpdatePreference.WantPositionChanged() && mDocShell) { mDocShell->RemoveWeakScrollObserver(this); mDocShell->RemoveWeakReflowObserver(this); } }
void IMEContentObserver::ObserveEditableNode() { MOZ_ASSERT(mSelection); MOZ_ASSERT(mRootContent); mUpdatePreference = mWidget->GetIMEUpdatePreference(); if (mUpdatePreference.WantSelectionChange()) { // add selection change listener nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); NS_ENSURE_TRUE_VOID(selPrivate); nsresult rv = selPrivate->AddSelectionListener(this); NS_ENSURE_SUCCESS_VOID(rv); } if (mUpdatePreference.WantTextChange()) { // add text change observer mRootContent->AddMutationObserver(this); } if (mUpdatePreference.WantPositionChanged() && mDocShell) { // Add scroll position listener and reflow observer to detect position and // size changes mDocShell->AddWeakScrollObserver(this); mDocShell->AddWeakReflowObserver(this); } }
nsresult nsCaretAccessible::ClearControlSelectionListener() { nsCOMPtr<nsISelectionController> controller = GetSelectionControllerForNode(mCurrentControl); mCurrentControl = nsnull; if (!controller) return NS_OK; // Remove 'this' registered as selection listener for the normal selection. nsCOMPtr<nsISelection> normalSel; controller->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(normalSel)); nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(normalSel)); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); nsresult rv = selPrivate->RemoveSelectionListener(this); NS_ENSURE_SUCCESS(rv, rv); // Remove 'this' registered as selection listener for the spellcheck // selection. nsCOMPtr<nsISelection> spellcheckSel; controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, getter_AddRefs(spellcheckSel)); selPrivate = do_QueryInterface(spellcheckSel); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); return selPrivate->RemoveSelectionListener(this); }
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; }
void nsTextStateManager::Destroy(void) { if (mSel) { nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel)); if (selPrivate) selPrivate->RemoveSelectionListener(this); mSel = nsnull; } if (mRootContent) { mRootContent->RemoveMutationObserver(this); mRootContent = nsnull; } mEditableNode = nsnull; mWidget = nsnull; }
nsresult nsCaretAccessible::SetControlSelectionListener(nsIContent *aCurrentNode) { NS_ENSURE_TRUE(mRootAccessible, NS_ERROR_FAILURE); ClearControlSelectionListener(); mCurrentControl = aCurrentNode; mLastTextAccessible = nsnull; // When focus moves such that the caret is part of a new frame selection // this removes the old selection listener and attaches a new one for // the current focus. nsCOMPtr<nsISelectionController> controller = GetSelectionControllerForNode(mCurrentControl); #ifdef DEBUG NS_ASSERTION(controller || aCurrentNode->IsNodeOfType(nsINode::eDOCUMENT), "No selection controller for non document node!"); #endif if (!controller) return NS_OK; // Register 'this' as selection listener for the normal selection. nsCOMPtr<nsISelection> normalSel; controller->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(normalSel)); nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(normalSel)); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); nsresult rv = selPrivate->AddSelectionListener(this); NS_ENSURE_SUCCESS(rv, rv); // Register 'this' as selection listener for the spellcheck selection. nsCOMPtr<nsISelection> spellcheckSel; controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, getter_AddRefs(spellcheckSel)); selPrivate = do_QueryInterface(spellcheckSel); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); return selPrivate->AddSelectionListener(this); }
nsresult nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent) { aEvent->mSucceeded = false; // Get selection to manipulate // XXX why do we need to get them from ISM? This method should work fine // without ISM. nsresult rv = nsIMEStateManager:: GetFocusSelectionAndRoot(getter_AddRefs(mSelection), getter_AddRefs(mRootContent)); if (rv != NS_ERROR_NOT_AVAILABLE) { NS_ENSURE_SUCCESS(rv, rv); } else { rv = Init(aEvent); NS_ENSURE_SUCCESS(rv, rv); } // Get range from offset and length nsRefPtr<nsRange> range = new nsRange(); NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY); rv = SetRangeFromFlatTextOffset(range, aEvent->mOffset, aEvent->mLength, aEvent->mExpandToClusterBoundary); NS_ENSURE_SUCCESS(rv, rv); nsINode* startNode = range->GetStartParent(); nsINode* endNode = range->GetEndParent(); PRInt32 startOffset = range->StartOffset(); PRInt32 endOffset = range->EndOffset(); AdjustRangeForSelection(mRootContent, &startNode, &startOffset); AdjustRangeForSelection(mRootContent, &endNode, &endOffset); nsCOMPtr<nsIDOMNode> startDomNode(do_QueryInterface(startNode)); nsCOMPtr<nsIDOMNode> endDomNode(do_QueryInterface(endNode)); NS_ENSURE_TRUE(startDomNode && endDomNode, NS_ERROR_UNEXPECTED); nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); selPrivate->StartBatchChanges(); // Clear selection first before setting rv = mSelection->RemoveAllRanges(); // Need to call EndBatchChanges at the end even if call failed if (NS_SUCCEEDED(rv)) { if (aEvent->mReversed) { rv = mSelection->Collapse(endDomNode, endOffset); } else { rv = mSelection->Collapse(startDomNode, startOffset); } if (NS_SUCCEEDED(rv) && (startDomNode != endDomNode || startOffset != endOffset)) { if (aEvent->mReversed) { rv = mSelection->Extend(startDomNode, startOffset); } else { rv = mSelection->Extend(endDomNode, endOffset); } } } selPrivate->EndBatchChanges(); NS_ENSURE_SUCCESS(rv, rv); selPrivate->ScrollIntoViewInternal( nsISelectionController::SELECTION_FOCUS_REGION, false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis()); aEvent->mSucceeded = true; return NS_OK; }
nsresult nsTextEditRules::WillInsertText(PRInt32 aAction, nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled, const nsAString *inString, nsAString *outString, PRInt32 aMaxLength) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } if (inString->IsEmpty() && (aAction != kInsertTextIME)) { // HACK: this is a fix for bug 19395 // I can't outlaw all empty insertions // because IME transaction depend on them // There is more work to do to make the // world safe for IME. *aCancel = PR_TRUE; *aHandled = PR_FALSE; return NS_OK; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_TRUE; // handle docs with a max length // NOTE, this function copies inString into outString for us. PRBool truncated = PR_FALSE; nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength, &truncated); NS_ENSURE_SUCCESS(res, res); if (truncated && outString->IsEmpty()) { *aCancel = PR_TRUE; return NS_OK; } PRUint32 start = 0; PRUint32 end = 0; // handle password field docs if (IsPasswordEditor()) { res = mEditor->GetTextSelectionOffsets(aSelection, start, end); NS_ASSERTION((NS_SUCCEEDED(res)), "getTextSelectionOffsets failed!"); NS_ENSURE_SUCCESS(res, res); } // if the selection isn't collapsed, delete it. PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); NS_ENSURE_SUCCESS(res, res); if (!bCollapsed) { res = mEditor->DeleteSelection(nsIEditor::eNone); NS_ENSURE_SUCCESS(res, res); } res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; // handle password field data // this has the side effect of changing all the characters in aOutString // to the replacement character if (IsPasswordEditor()) { if (aAction == kInsertTextIME) { res = RemoveIMETextFromPWBuf(start, outString); NS_ENSURE_SUCCESS(res, res); } } // People have lots of different ideas about what text fields // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935. // The six possible options are: // 0. paste newlines intact // 1. paste up to the first newline (default) // 2. replace newlines with spaces // 3. strip newlines // 4. replace with commas // 5. strip newlines and surrounding whitespace // So find out what we're expected to do: if (IsSingleLineEditor()) { nsAutoString tString(*outString); HandleNewLines(tString, mEditor->mNewlineHandling); outString->Assign(tString); } if (IsPasswordEditor()) { // manage the password buffer mPasswordText.Insert(*outString, start); nsCOMPtr<nsILookAndFeel> lookAndFeel = do_GetService(kLookAndFeelCID); if (lookAndFeel->GetEchoPassword() && !DontEchoPassword()) { HideLastPWInput(); mLastStart = start; mLastLength = outString->Length(); if (mTimer) { mTimer->Cancel(); } else { mTimer = do_CreateInstance("@mozilla.org/timer;1", &res); NS_ENSURE_SUCCESS(res, res); } mTimer->InitWithCallback(this, 600, nsITimer::TYPE_ONE_SHOT); } else { res = FillBufWithPWChars(outString, outString->Length()); NS_ENSURE_SUCCESS(res, res); } } // get the (collapsed) selection location nsCOMPtr<nsIDOMNode> selNode; PRInt32 selOffset; res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // don't put text in places that can't have it if (!mEditor->IsTextNode(selNode) && !mEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text"))) return NS_ERROR_FAILURE; // we need to get the doc nsCOMPtr<nsIDOMDocument>doc; res = mEditor->GetDocument(getter_AddRefs(doc)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); if (aAction == kInsertTextIME) { res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc); NS_ENSURE_SUCCESS(res, res); } else // aAction == kInsertText { // find where we are nsCOMPtr<nsIDOMNode> curNode = selNode; PRInt32 curOffset = selOffset; // don't spaz my selection in subtransactions nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); res = mEditor->InsertTextImpl(*outString, address_of(curNode), &curOffset, doc); NS_ENSURE_SUCCESS(res, res); if (curNode) { // Make the caret attach to the inserted text, unless this text ends with a LF, // in which case make the caret attach to the next line. PRBool endsWithLF = !outString->IsEmpty() && outString->Last() == nsCRT::LF; nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection)); selPrivate->SetInterlinePosition(endsWithLF); aSelection->Collapse(curNode, curOffset); } } ASSERT_PASSWORD_LENGTHS_EQUAL() return res; }
nsresult nsTextEditRules::WillInsertText(PRInt32 aAction, nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled, const nsAString *inString, nsAString *outString, PRInt32 aMaxLength) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } if (inString->IsEmpty() && (aAction != kInsertTextIME)) { // HACK: this is a fix for bug 19395 // I can't outlaw all empty insertions // because IME transaction depend on them // There is more work to do to make the // world safe for IME. *aCancel = PR_TRUE; *aHandled = PR_FALSE; return NS_OK; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_TRUE; // handle docs with a max length // NOTE, this function copies inString into outString for us. nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength); NS_ENSURE_SUCCESS(res, res); PRUint32 start = 0; PRUint32 end = 0; // handle password field docs if (IsPasswordEditor()) { res = mEditor->GetTextSelectionOffsets(aSelection, start, end); NS_ASSERTION((NS_SUCCEEDED(res)), "getTextSelectionOffsets failed!"); NS_ENSURE_SUCCESS(res, res); } // if the selection isn't collapsed, delete it. PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); NS_ENSURE_SUCCESS(res, res); if (!bCollapsed) { res = mEditor->DeleteSelection(nsIEditor::eNone); NS_ENSURE_SUCCESS(res, res); } res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; // handle password field data // this has the side effect of changing all the characters in aOutString // to the replacement character if (IsPasswordEditor()) { if (aAction == kInsertTextIME) { res = RemoveIMETextFromPWBuf(start, outString); NS_ENSURE_SUCCESS(res, res); } } // People have lots of different ideas about what text fields // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935. // The six possible options are: // 0. paste newlines intact // 1. paste up to the first newline (default) // 2. replace newlines with spaces // 3. strip newlines // 4. replace with commas // 5. strip newlines and surrounding whitespace // So find out what we're expected to do: if (IsSingleLineEditor()) { nsAutoString tString(*outString); HandleNewLines(tString, mEditor->mNewlineHandling); outString->Assign(tString); } if (IsPasswordEditor()) { // manage the password buffer mPasswordText.Insert(*outString, start); nsCOMPtr<nsILookAndFeel> lookAndFeel = do_GetService(kLookAndFeelCID); if (lookAndFeel->GetEchoPassword() && !DontEchoPassword()) { HideLastPWInput(); mLastStart = start; mLastLength = outString->Length(); if (mTimer) { mTimer->Cancel(); } else { mTimer = do_CreateInstance("@mozilla.org/timer;1", &res); NS_ENSURE_SUCCESS(res, res); } mTimer->InitWithCallback(this, 600, nsITimer::TYPE_ONE_SHOT); } else { res = FillBufWithPWChars(outString, outString->Length()); NS_ENSURE_SUCCESS(res, res); } } // get the (collapsed) selection location nsCOMPtr<nsIDOMNode> selNode; PRInt32 selOffset; res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // don't put text in places that can't have it if (!mEditor->IsTextNode(selNode) && !mEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text"))) return NS_ERROR_FAILURE; // we need to get the doc nsCOMPtr<nsIDOMDocument>doc; res = mEditor->GetDocument(getter_AddRefs(doc)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); if (aAction == kInsertTextIME) { res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc); NS_ENSURE_SUCCESS(res, res); } else // aAction == kInsertText { // find where we are nsCOMPtr<nsIDOMNode> curNode = selNode; PRInt32 curOffset = selOffset; // is our text going to be PREformatted? // We remember this so that we know how to handle tabs. PRBool isPRE; res = mEditor->IsPreformatted(selNode, &isPRE); NS_ENSURE_SUCCESS(res, res); // don't spaz my selection in subtransactions nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); nsString tString(*outString); const PRUnichar *unicodeBuf = tString.get(); nsCOMPtr<nsIDOMNode> unused; PRInt32 pos = 0; // for efficiency, break out the pre case separately. This is because // it's a lot cheaper to search the input string for only newlines than // it is to search for both tabs and newlines. if (isPRE) { while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length())) { PRInt32 oldPos = pos; PRInt32 subStrLen; pos = tString.FindChar(nsCRT::LF, oldPos); if (pos != -1) { subStrLen = pos - oldPos; // if first char is newline, then use just it if (subStrLen == 0) subStrLen = 1; } else { subStrLen = tString.Length() - oldPos; pos = tString.Length(); } nsDependentSubstring subStr(tString, oldPos, subStrLen); // is it a return? if (subStr.EqualsLiteral(LFSTR)) { if (IsSingleLineEditor()) { NS_ASSERTION((mEditor->mNewlineHandling == nsIPlaintextEditor::eNewlinesPasteIntact), "Newline improperly getting into single-line edit field!"); res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); } else { res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); // If the newline is the last character in the string, and the BR we // just inserted is the last node in the content tree, we need to add // a mozBR so that a blank line is created. if (NS_SUCCEEDED(res) && curNode && pos == (PRInt32)(tString.Length() - 1)) { nsCOMPtr<nsIDOMNode> nextChild = mEditor->GetChildAt(curNode, curOffset); if (!nextChild) { // We must be at the end since there isn't a nextChild. // // curNode and curOffset should be set to the position after // the BR we added above, so just create a mozBR at that position. // // Note that we don't update curOffset after we've created/inserted // the mozBR since we never want the selection to be placed after it. res = CreateMozBR(curNode, curOffset, address_of(unused)); } } } pos++; } else { res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); } NS_ENSURE_SUCCESS(res, res); } } else { char specialChars[] = {TAB, nsCRT::LF, 0}; while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length())) { PRInt32 oldPos = pos; PRInt32 subStrLen; pos = tString.FindCharInSet(specialChars, oldPos); if (pos != -1) { subStrLen = pos - oldPos; // if first char is newline, then use just it if (subStrLen == 0) subStrLen = 1; } else { subStrLen = tString.Length() - oldPos; pos = tString.Length(); } nsDependentSubstring subStr(tString, oldPos, subStrLen); // is it a tab? if (subStr.EqualsLiteral("\t")) { res = mEditor->InsertTextImpl(NS_LITERAL_STRING(" "), address_of(curNode), &curOffset, doc); pos++; } // is it a return? else if (subStr.EqualsLiteral(LFSTR)) { res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); pos++; } else { res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); } NS_ENSURE_SUCCESS(res, res); } } outString->Assign(tString); if (curNode) { aSelection->Collapse(curNode, curOffset); // Make the caret attach to the inserted text, unless this text ends with a LF, // in which case make the caret attach to the next line. PRBool endsWithLF = !tString.IsEmpty() && tString.get()[tString.Length() - 1] == nsCRT::LF; nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection)); selPrivate->SetInterlinePosition(endsWithLF); } } ASSERT_PASSWORD_LENGTHS_EQUAL() return res; }
nsresult nsTextStateManager::Init(nsIWidget* aWidget, nsPresContext* aPresContext, nsINode* aNode, PRBool aWantUpdates) { mWidget = aWidget; if (!aWantUpdates) { mEditableNode = aNode; return NS_OK; } nsIPresShell* presShell = aPresContext->PresShell(); // get selection and root content nsCOMPtr<nsISelectionController> selCon; if (aNode->IsNodeOfType(nsINode::eCONTENT)) { nsIFrame* frame = static_cast<nsIContent*>(aNode)->GetPrimaryFrame(); NS_ENSURE_TRUE(frame, NS_ERROR_UNEXPECTED); frame->GetSelectionController(aPresContext, getter_AddRefs(selCon)); } else { // aNode is a document selCon = do_QueryInterface(presShell); } NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); nsCOMPtr<nsISelection> sel; nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(sel)); NS_ENSURE_TRUE(sel, NS_ERROR_UNEXPECTED); nsCOMPtr<nsIDOMRange> selDomRange; rv = sel->GetRangeAt(0, getter_AddRefs(selDomRange)); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIRange> selRange(do_QueryInterface(selDomRange)); NS_ENSURE_TRUE(selRange && selRange->GetStartParent(), NS_ERROR_UNEXPECTED); mRootContent = selRange->GetStartParent()-> GetSelectionRootContent(presShell); } else { mRootContent = aNode->GetSelectionRootContent(presShell); } if (!mRootContent && aNode->IsNodeOfType(nsINode::eDOCUMENT)) { // The document node is editable, but there are no contents, this document // is not editable. return NS_ERROR_NOT_AVAILABLE; } NS_ENSURE_TRUE(mRootContent, NS_ERROR_UNEXPECTED); // add text change observer mRootContent->AddMutationObserver(this); // add selection change listener nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(sel)); NS_ENSURE_TRUE(selPrivate, NS_ERROR_UNEXPECTED); rv = selPrivate->AddSelectionListener(this); NS_ENSURE_SUCCESS(rv, rv); mSel = sel; mEditableNode = aNode; return NS_OK; }