michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" // for DebugOnly michael@0: michael@0: #include // for nullptr, stdout michael@0: #include // for strcmp michael@0: michael@0: #include "ChangeAttributeTxn.h" // for ChangeAttributeTxn michael@0: #include "CreateElementTxn.h" // for CreateElementTxn michael@0: #include "DeleteNodeTxn.h" // for DeleteNodeTxn michael@0: #include "DeleteRangeTxn.h" // for DeleteRangeTxn michael@0: #include "DeleteTextTxn.h" // for DeleteTextTxn michael@0: #include "EditAggregateTxn.h" // for EditAggregateTxn michael@0: #include "EditTxn.h" // for EditTxn michael@0: #include "IMETextTxn.h" // for IMETextTxn michael@0: #include "InsertElementTxn.h" // for InsertElementTxn michael@0: #include "InsertTextTxn.h" // for InsertTextTxn michael@0: #include "JoinElementTxn.h" // for JoinElementTxn michael@0: #include "PlaceholderTxn.h" // for PlaceholderTxn michael@0: #include "SplitElementTxn.h" // for SplitElementTxn michael@0: #include "mozFlushType.h" // for mozFlushType::Flush_Frames michael@0: #include "mozISpellCheckingEngine.h" michael@0: #include "mozInlineSpellChecker.h" // for mozInlineSpellChecker michael@0: #include "mozilla/IMEStateManager.h" // for IMEStateManager michael@0: #include "mozilla/Preferences.h" // for Preferences michael@0: #include "mozilla/dom/Selection.h" // for Selection, etc michael@0: #include "mozilla/Services.h" // for GetObserverService michael@0: #include "mozilla/TextComposition.h" // for TextComposition michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement michael@0: #include "mozilla/mozalloc.h" // for operator new, etc michael@0: #include "nsAString.h" // for nsAString_internal::Length, etc michael@0: #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker michael@0: #include "nsCaret.h" // for nsCaret michael@0: #include "nsCaseTreatment.h" michael@0: #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc michael@0: #include "nsComponentManagerUtils.h" // for do_CreateInstance michael@0: #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle michael@0: #include "nsContentUtils.h" // for nsContentUtils michael@0: #include "nsDOMString.h" // for DOMStringIsNull michael@0: #include "nsDebug.h" // for NS_ENSURE_TRUE, etc michael@0: #include "nsEditProperty.h" // for nsEditProperty, etc michael@0: #include "nsEditor.h" michael@0: #include "nsEditorEventListener.h" // for nsEditorEventListener michael@0: #include "nsEditorUtils.h" // for nsAutoRules, etc michael@0: #include "nsError.h" // for NS_OK, etc michael@0: #include "nsFocusManager.h" // for nsFocusManager michael@0: #include "nsFrameSelection.h" // for nsFrameSelection michael@0: #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir michael@0: #include "nsIAbsorbingTransaction.h" // for nsIAbsorbingTransaction michael@0: #include "nsIAtom.h" // for nsIAtom michael@0: #include "nsIContent.h" // for nsIContent michael@0: #include "nsIDOMAttr.h" // for nsIDOMAttr michael@0: #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData michael@0: #include "nsIDOMDocument.h" // for nsIDOMDocument michael@0: #include "nsIDOMElement.h" // for nsIDOMElement michael@0: #include "nsIDOMEvent.h" // for nsIDOMEvent michael@0: #include "nsIDOMEventListener.h" // for nsIDOMEventListener michael@0: #include "nsIDOMEventTarget.h" // for nsIDOMEventTarget michael@0: #include "nsIDOMHTMLElement.h" // for nsIDOMHTMLElement michael@0: #include "nsIDOMKeyEvent.h" // for nsIDOMKeyEvent, etc michael@0: #include "nsIDOMMozNamedAttrMap.h" // for nsIDOMMozNamedAttrMap michael@0: #include "nsIDOMMouseEvent.h" // for nsIDOMMouseEvent michael@0: #include "nsIDOMNode.h" // for nsIDOMNode, etc michael@0: #include "nsIDOMNodeList.h" // for nsIDOMNodeList michael@0: #include "nsIDOMRange.h" // for nsIDOMRange michael@0: #include "nsIDOMText.h" // for nsIDOMText michael@0: #include "nsIDocument.h" // for nsIDocument michael@0: #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener michael@0: #include "nsIEditActionListener.h" // for nsIEditActionListener michael@0: #include "nsIEditorObserver.h" // for nsIEditorObserver michael@0: #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck michael@0: #include "nsIFrame.h" // for nsIFrame michael@0: #include "nsIHTMLDocument.h" // for nsIHTMLDocument michael@0: #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc michael@0: #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc michael@0: #include "nsINode.h" // for nsINode, etc michael@0: #include "nsIObserverService.h" // for nsIObserverService michael@0: #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc michael@0: #include "nsIPresShell.h" // for nsIPresShell michael@0: #include "nsISelection.h" // for nsISelection, etc michael@0: #include "nsISelectionController.h" // for nsISelectionController, etc michael@0: #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc michael@0: #include "nsISelectionPrivate.h" // for nsISelectionPrivate, etc michael@0: #include "nsISupportsBase.h" // for nsISupports michael@0: #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF michael@0: #include "nsITransaction.h" // for nsITransaction michael@0: #include "nsITransactionManager.h" michael@0: #include "nsIWeakReference.h" // for nsISupportsWeakReference michael@0: #include "nsIWidget.h" // for nsIWidget, IMEState, etc michael@0: #include "nsPIDOMWindow.h" // for nsPIDOMWindow michael@0: #include "nsPresContext.h" // for nsPresContext michael@0: #include "nsRange.h" // for nsRange michael@0: #include "nsReadableUtils.h" // for EmptyString, ToNewCString michael@0: #include "nsString.h" // for nsAutoString, nsString, etc michael@0: #include "nsStringFwd.h" // for nsAFlatString michael@0: #include "nsStyleConsts.h" // for NS_STYLE_DIRECTION_RTL, etc michael@0: #include "nsStyleContext.h" // for nsStyleContext michael@0: #include "nsStyleSheetTxns.h" // for AddStyleSheetTxn, etc michael@0: #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc michael@0: #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc michael@0: #include "nsTextEditUtils.h" // for nsTextEditUtils michael@0: #include "nsTextNode.h" // for nsTextNode michael@0: #include "nsThreadUtils.h" // for nsRunnable michael@0: #include "nsTransactionManager.h" // for nsTransactionManager michael@0: #include "prtime.h" // for PR_Now michael@0: michael@0: class nsIOutputStream; michael@0: class nsIParserService; michael@0: class nsITransferable; michael@0: michael@0: #ifdef DEBUG michael@0: #include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::widget; michael@0: michael@0: // Defined in nsEditorRegistration.cpp michael@0: extern nsIParserService *sParserService; michael@0: michael@0: //--------------------------------------------------------------------------- michael@0: // michael@0: // nsEditor: base editor class implementation michael@0: // michael@0: //--------------------------------------------------------------------------- michael@0: michael@0: nsEditor::nsEditor() michael@0: : mPlaceHolderName(nullptr) michael@0: , mSelState(nullptr) michael@0: , mPhonetic(nullptr) michael@0: , mModCount(0) michael@0: , mFlags(0) michael@0: , mUpdateCount(0) michael@0: , mPlaceHolderBatch(0) michael@0: , mAction(EditAction::none) michael@0: , mIMETextOffset(0) michael@0: , mDirection(eNone) michael@0: , mDocDirtyState(-1) michael@0: , mSpellcheckCheckboxState(eTriUnset) michael@0: , mShouldTxnSetSelection(true) michael@0: , mDidPreDestroy(false) michael@0: , mDidPostCreate(false) michael@0: , mDispatchInputEvent(true) michael@0: { michael@0: } michael@0: michael@0: nsEditor::~nsEditor() michael@0: { michael@0: NS_ASSERTION(!mDocWeak || mDidPreDestroy, "Why PreDestroy hasn't been called?"); michael@0: michael@0: mTxnMgr = nullptr; michael@0: michael@0: delete mPhonetic; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsEditor) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsEditor) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mTxnMgr) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMETextNode) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorObservers) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventListener) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsEditor) michael@0: nsIDocument* currentDoc = michael@0: tmp->mRootElement ? tmp->mRootElement->GetCurrentDoc() : nullptr; michael@0: if (currentDoc && michael@0: nsCCUncollectableMarker::InGeneration(cb, currentDoc->GetMarkedCCGeneration())) { michael@0: return NS_SUCCESS_INTERRUPTED_TRAVERSE; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTxnMgr) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMETextNode) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorObservers) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIPhonetic) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditorIMESupport) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditor) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditor) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Init(nsIDOMDocument *aDoc, nsIContent *aRoot, michael@0: nsISelectionController *aSelCon, uint32_t aFlags, michael@0: const nsAString& aValue) michael@0: { michael@0: NS_PRECONDITION(aDoc, "bad arg"); michael@0: if (!aDoc) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: // First only set flags, but other stuff shouldn't be initialized now. michael@0: // Don't move this call after initializing mDocWeak. michael@0: // SetFlags() can check whether it's called during initialization or not by michael@0: // them. Note that SetFlags() will be called by PostCreate(). michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: SetFlags(aFlags); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed"); michael@0: michael@0: mDocWeak = do_GetWeakReference(aDoc); // weak reference to doc michael@0: // HTML editors currently don't have their own selection controller, michael@0: // so they'll pass null as aSelCon, and we'll get the selection controller michael@0: // off of the presshell. michael@0: nsCOMPtr selCon; michael@0: if (aSelCon) { michael@0: mSelConWeak = do_GetWeakReference(aSelCon); // weak reference to selectioncontroller michael@0: selCon = aSelCon; michael@0: } else { michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: selCon = do_QueryInterface(presShell); michael@0: } michael@0: NS_ASSERTION(selCon, "Selection controller should be available at this point"); michael@0: michael@0: //set up root element if we are passed one. michael@0: if (aRoot) michael@0: mRootElement = do_QueryInterface(aRoot); michael@0: michael@0: mUpdateCount=0; michael@0: michael@0: /* initialize IME stuff */ michael@0: mIMETextNode = nullptr; michael@0: mIMETextOffset = 0; michael@0: /* Show the caret */ michael@0: selCon->SetCaretReadOnly(false); michael@0: selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); michael@0: michael@0: selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);//we want to see all the selection reflected to user michael@0: michael@0: NS_POSTCONDITION(mDocWeak, "bad state"); michael@0: michael@0: // Make sure that the editor will be destroyed properly michael@0: mDidPreDestroy = false; michael@0: // Make sure that the ediotr will be created properly michael@0: mDidPostCreate = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::PostCreate() michael@0: { michael@0: // Synchronize some stuff for the flags. SetFlags() will initialize michael@0: // something by the flag difference. This is first time of that, so, all michael@0: // initializations must be run. For such reason, we need to invert mFlags michael@0: // value first. michael@0: mFlags = ~mFlags; michael@0: nsresult rv = SetFlags(~mFlags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // These operations only need to happen on the first PostCreate call michael@0: if (!mDidPostCreate) { michael@0: mDidPostCreate = true; michael@0: michael@0: // Set up listeners michael@0: CreateEventListeners(); michael@0: rv = InstallEventListeners(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // nuke the modification count, so the doc appears unmodified michael@0: // do this before we notify listeners michael@0: ResetModificationCount(); michael@0: michael@0: // update the UI with our state michael@0: NotifyDocumentListeners(eDocumentCreated); michael@0: NotifyDocumentListeners(eDocumentStateChanged); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(this, michael@0: SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION, michael@0: false); michael@0: } michael@0: } michael@0: michael@0: // update nsTextStateManager and caret if we have focus michael@0: nsCOMPtr focusedContent = GetFocusedContent(); michael@0: if (focusedContent) { michael@0: nsCOMPtr target = do_QueryInterface(focusedContent); michael@0: if (target) { michael@0: InitializeSelection(target); michael@0: } michael@0: michael@0: // If the text control gets reframed during focus, Focus() would not be michael@0: // called, so take a chance here to see if we need to spell check the text michael@0: // control. michael@0: nsEditorEventListener* listener = michael@0: reinterpret_cast (mEventListener.get()); michael@0: listener->SpellCheckIfNeeded(); michael@0: michael@0: IMEState newState; michael@0: rv = GetPreferredIMEState(&newState); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: nsCOMPtr content = GetFocusedContentForIME(); michael@0: IMEStateManager::UpdateIMEState(newState, content); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ michael@0: void michael@0: nsEditor::CreateEventListeners() michael@0: { michael@0: // Don't create the handler twice michael@0: if (!mEventListener) { michael@0: mEventListener = new nsEditorEventListener(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::InstallEventListeners() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak && mEventListener, michael@0: NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Initialize the event target. michael@0: nsCOMPtr rootContent = GetRoot(); michael@0: NS_ENSURE_TRUE(rootContent, NS_ERROR_NOT_AVAILABLE); michael@0: mEventTarget = do_QueryInterface(rootContent->GetParent()); michael@0: NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsEditorEventListener* listener = michael@0: reinterpret_cast(mEventListener.get()); michael@0: return listener->Connect(this); michael@0: } michael@0: michael@0: void michael@0: nsEditor::RemoveEventListeners() michael@0: { michael@0: if (!mDocWeak || !mEventListener) { michael@0: return; michael@0: } michael@0: reinterpret_cast(mEventListener.get())->Disconnect(); michael@0: if (mComposition) { michael@0: mComposition->EndHandlingComposition(this); michael@0: mComposition = nullptr; michael@0: } michael@0: mEventTarget = nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::GetDesiredSpellCheckState() michael@0: { michael@0: // Check user override on this element michael@0: if (mSpellcheckCheckboxState != eTriUnset) { michael@0: return (mSpellcheckCheckboxState == eTriTrue); michael@0: } michael@0: michael@0: // Check user preferences michael@0: int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1); michael@0: michael@0: if (spellcheckLevel == 0) { michael@0: return false; // Spellchecking forced off globally michael@0: } michael@0: michael@0: if (!CanEnableSpellCheck()) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: if (presShell) { michael@0: nsPresContext* context = presShell->GetPresContext(); michael@0: if (context && !context->IsDynamic()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Check DOM state michael@0: nsCOMPtr content = GetExposedRoot(); michael@0: if (!content) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr element = do_QueryInterface(content); michael@0: if (!element) { michael@0: return false; michael@0: } michael@0: michael@0: if (!IsPlaintextEditor()) { michael@0: // Some of the page content might be editable and some not, if spellcheck= michael@0: // is explicitly set anywhere, so if there's anything editable on the page, michael@0: // return true and let the spellchecker figure it out. michael@0: nsCOMPtr doc = do_QueryInterface(content->GetCurrentDoc()); michael@0: return doc && doc->IsEditingOn(); michael@0: } michael@0: michael@0: bool enable; michael@0: element->GetSpellcheck(&enable); michael@0: michael@0: return enable; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::PreDestroy(bool aDestroyingFrames) michael@0: { michael@0: if (mDidPreDestroy) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->RemoveObserver(this, michael@0: SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION); michael@0: } michael@0: michael@0: // Let spellchecker clean up its observers etc. It is important not to michael@0: // actually free the spellchecker here, since the spellchecker could have michael@0: // caused flush notifications, which could have gotten here if a textbox michael@0: // is being removed. Setting the spellchecker to nullptr could free the michael@0: // object that is still in use! It will be freed when the editor is michael@0: // destroyed. michael@0: if (mInlineSpellChecker) michael@0: mInlineSpellChecker->Cleanup(aDestroyingFrames); michael@0: michael@0: // tell our listeners that the doc is going away michael@0: NotifyDocumentListeners(eDocumentToBeDestroyed); michael@0: michael@0: // Unregister event listeners michael@0: RemoveEventListeners(); michael@0: mActionListeners.Clear(); michael@0: mEditorObservers.Clear(); michael@0: mDocStateListeners.Clear(); michael@0: mInlineSpellChecker = nullptr; michael@0: mSpellcheckCheckboxState = eTriUnset; michael@0: mRootElement = nullptr; michael@0: michael@0: mDidPreDestroy = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetFlags(uint32_t *aFlags) michael@0: { michael@0: *aFlags = mFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetFlags(uint32_t aFlags) michael@0: { michael@0: if (mFlags == aFlags) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool spellcheckerWasEnabled = CanEnableSpellCheck(); michael@0: mFlags = aFlags; michael@0: michael@0: if (!mDocWeak) { michael@0: // If we're initializing, we shouldn't do anything now. michael@0: // SetFlags() will be called by PostCreate(), michael@0: // we should synchronize some stuff for the flags at that time. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The flag change may cause the spellchecker state change michael@0: if (CanEnableSpellCheck() != spellcheckerWasEnabled) { michael@0: nsresult rv = SyncRealTimeSpell(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // If this is called from PostCreate(), it will update the IME state if it's michael@0: // necessary. michael@0: if (!mDidPostCreate) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Might be changing editable state, so, we need to reset current IME state michael@0: // if we're focused and the flag change causes IME state change. michael@0: nsCOMPtr focusedContent = GetFocusedContent(); michael@0: if (focusedContent) { michael@0: IMEState newState; michael@0: nsresult rv = GetPreferredIMEState(&newState); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // NOTE: When the enabled state isn't going to be modified, this method michael@0: // is going to do nothing. michael@0: nsCOMPtr content = GetFocusedContentForIME(); michael@0: IMEStateManager::UpdateIMEState(newState, content); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetIsSelectionEditable(bool *aIsSelectionEditable) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsSelectionEditable); michael@0: michael@0: // get current selection michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // XXX we just check that the anchor node is editable at the moment michael@0: // we should check that all nodes in the selection are editable michael@0: nsCOMPtr anchorNode; michael@0: selection->GetAnchorNode(getter_AddRefs(anchorNode)); michael@0: *aIsSelectionEditable = anchorNode && IsEditable(anchorNode); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsDocumentEditable); michael@0: nsCOMPtr doc = GetDOMDocument(); michael@0: *aIsDocumentEditable = !!doc; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsEditor::GetDocument() michael@0: { michael@0: NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized"); michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: return doc.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsEditor::GetDOMDocument() michael@0: { michael@0: NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized"); michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: return doc.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetDocument(nsIDOMDocument **aDoc) michael@0: { michael@0: nsCOMPtr doc = GetDOMDocument(); michael@0: doc.forget(aDoc); michael@0: return *aDoc ? NS_OK : NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsEditor::GetPresShell() michael@0: { michael@0: NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak"); michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: nsCOMPtr ps = doc->GetShell(); michael@0: return ps.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsEditor::GetWidget() michael@0: { michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, nullptr); michael@0: nsPresContext* pc = ps->GetPresContext(); michael@0: NS_ENSURE_TRUE(pc, nullptr); michael@0: nsCOMPtr widget = pc->GetRootWidget(); michael@0: NS_ENSURE_TRUE(widget.get(), nullptr); michael@0: return widget.forget(); michael@0: } michael@0: michael@0: /* attribute string contentsMIMEType; */ michael@0: NS_IMETHODIMP michael@0: nsEditor::GetContentsMIMEType(char * *aContentsMIMEType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aContentsMIMEType); michael@0: *aContentsMIMEType = ToNewCString(mContentMIMEType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetContentsMIMEType(const char * aContentsMIMEType) michael@0: { michael@0: mContentMIMEType.Assign(aContentsMIMEType ? aContentsMIMEType : ""); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetSelectionController(nsISelectionController **aSel) michael@0: { michael@0: NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER); michael@0: *aSel = nullptr; // init out param michael@0: nsCOMPtr selCon; michael@0: if (mSelConWeak) { michael@0: selCon = do_QueryReferent(mSelConWeak); michael@0: } else { michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: selCon = do_QueryInterface(presShell); michael@0: } michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); michael@0: NS_ADDREF(*aSel = selCon); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DeleteSelection(EDirection aAction, EStripWrappers aStripWrappers) michael@0: { michael@0: MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); michael@0: return DeleteSelectionImpl(aAction, aStripWrappers); michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetSelection(nsISelection **aSelection) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: *aSelection = nullptr; michael@0: nsCOMPtr selcon; michael@0: GetSelectionController(getter_AddRefs(selcon)); michael@0: NS_ENSURE_TRUE(selcon, NS_ERROR_NOT_INITIALIZED); michael@0: return selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, aSelection); // does an addref michael@0: } michael@0: michael@0: Selection* michael@0: nsEditor::GetSelection() michael@0: { michael@0: nsCOMPtr sel; michael@0: nsresult res = GetSelection(getter_AddRefs(sel)); michael@0: NS_ENSURE_SUCCESS(res, nullptr); michael@0: michael@0: return static_cast(sel.get()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DoTransaction(nsITransaction* aTxn) michael@0: { michael@0: if (mPlaceHolderBatch && !mPlaceHolderTxn) { michael@0: nsCOMPtr plcTxn = new PlaceholderTxn(); michael@0: michael@0: // save off weak reference to placeholder txn michael@0: mPlaceHolderTxn = do_GetWeakReference(plcTxn); michael@0: plcTxn->Init(mPlaceHolderName, mSelState, this); michael@0: // placeholder txn took ownership of this pointer michael@0: mSelState = nullptr; michael@0: michael@0: // QI to an nsITransaction since that's what DoTransaction() expects michael@0: nsCOMPtr theTxn = do_QueryInterface(plcTxn); michael@0: // we will recurse, but will not hit this case in the nested call michael@0: DoTransaction(theTxn); michael@0: michael@0: if (mTxnMgr) { michael@0: nsCOMPtr topTxn = mTxnMgr->PeekUndoStack(); michael@0: if (topTxn) { michael@0: plcTxn = do_QueryInterface(topTxn); michael@0: if (plcTxn) { michael@0: // there is a placeholder transaction on top of the undo stack. It michael@0: // is either the one we just created, or an earlier one that we are michael@0: // now merging into. From here on out remember this placeholder michael@0: // instead of the one we just created. michael@0: mPlaceHolderTxn = do_GetWeakReference(plcTxn); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aTxn) { michael@0: // XXX: Why are we doing selection specific batching stuff here? michael@0: // XXX: Most entry points into the editor have auto variables that michael@0: // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make michael@0: // XXX: these selection batch calls no-ops. michael@0: // XXX: michael@0: // XXX: I suspect that this was placed here to avoid multiple michael@0: // XXX: selection changed notifications from happening until after michael@0: // XXX: the transaction was done. I suppose that can still happen michael@0: // XXX: if an embedding application called DoTransaction() directly michael@0: // XXX: to pump its own transactions through the system, but in that michael@0: // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or michael@0: // XXX: its auto equivalent nsAutoUpdateViewBatch to ensure that michael@0: // XXX: selection listeners have access to accurate frame data? michael@0: // XXX: michael@0: // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls michael@0: // XXX: we will need to make sure that they are disabled during michael@0: // XXX: the init of the editor for text widgets to avoid layout michael@0: // XXX: re-entry during initial reflow. - kin michael@0: michael@0: // get the selection and start a batch change michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: selection->StartBatchChanges(); michael@0: michael@0: nsresult res; michael@0: if (mTxnMgr) { michael@0: res = mTxnMgr->DoTransaction(aTxn); michael@0: } else { michael@0: res = aTxn->DoTransaction(); michael@0: } michael@0: if (NS_SUCCEEDED(res)) { michael@0: DoAfterDoTransaction(aTxn); michael@0: } michael@0: michael@0: // no need to check res here, don't lose result of operation michael@0: selection->EndBatchChanges(); michael@0: michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::EnableUndo(bool aEnable) michael@0: { michael@0: if (aEnable) { michael@0: if (!mTxnMgr) { michael@0: mTxnMgr = new nsTransactionManager(); michael@0: } michael@0: mTxnMgr->SetMaxTransactionCount(-1); michael@0: } else if (mTxnMgr) { michael@0: // disable the transaction manager if it is enabled michael@0: mTxnMgr->Clear(); michael@0: mTxnMgr->SetMaxTransactionCount(0); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetNumberOfUndoItems(int32_t* aNumItems) michael@0: { michael@0: *aNumItems = 0; michael@0: return mTxnMgr ? mTxnMgr->GetNumberOfUndoItems(aNumItems) : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetNumberOfRedoItems(int32_t* aNumItems) michael@0: { michael@0: *aNumItems = 0; michael@0: return mTxnMgr ? mTxnMgr->GetNumberOfRedoItems(aNumItems) : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetTransactionManager(nsITransactionManager* *aTxnManager) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aTxnManager); michael@0: michael@0: *aTxnManager = nullptr; michael@0: NS_ENSURE_TRUE(mTxnMgr, NS_ERROR_FAILURE); michael@0: michael@0: NS_ADDREF(*aTxnManager = mTxnMgr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetTransactionManager(nsITransactionManager *aTxnManager) michael@0: { michael@0: NS_ENSURE_TRUE(aTxnManager, NS_ERROR_FAILURE); michael@0: michael@0: // nsITransactionManager is builtinclass, so this is safe michael@0: mTxnMgr = static_cast(aTxnManager); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Undo(uint32_t aCount) michael@0: { michael@0: ForceCompositionEnd(); michael@0: michael@0: bool hasTxnMgr, hasTransaction = false; michael@0: CanUndo(&hasTxnMgr, &hasTransaction); michael@0: NS_ENSURE_TRUE(hasTransaction, NS_OK); michael@0: michael@0: nsAutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone); michael@0: michael@0: if (!mTxnMgr) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < aCount; ++i) { michael@0: nsresult rv = mTxnMgr->UndoTransaction(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: DoAfterUndoTransaction(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CanUndo(bool *aIsEnabled, bool *aCanUndo) michael@0: { michael@0: NS_ENSURE_TRUE(aIsEnabled && aCanUndo, NS_ERROR_NULL_POINTER); michael@0: *aIsEnabled = !!mTxnMgr; michael@0: if (*aIsEnabled) { michael@0: int32_t numTxns = 0; michael@0: mTxnMgr->GetNumberOfUndoItems(&numTxns); michael@0: *aCanUndo = !!numTxns; michael@0: } else { michael@0: *aCanUndo = false; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Redo(uint32_t aCount) michael@0: { michael@0: bool hasTxnMgr, hasTransaction = false; michael@0: CanRedo(&hasTxnMgr, &hasTransaction); michael@0: NS_ENSURE_TRUE(hasTransaction, NS_OK); michael@0: michael@0: nsAutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone); michael@0: michael@0: if (!mTxnMgr) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < aCount; ++i) { michael@0: nsresult rv = mTxnMgr->RedoTransaction(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: DoAfterRedoTransaction(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CanRedo(bool *aIsEnabled, bool *aCanRedo) michael@0: { michael@0: NS_ENSURE_TRUE(aIsEnabled && aCanRedo, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aIsEnabled = !!mTxnMgr; michael@0: if (*aIsEnabled) { michael@0: int32_t numTxns = 0; michael@0: mTxnMgr->GetNumberOfRedoItems(&numTxns); michael@0: *aCanRedo = !!numTxns; michael@0: } else { michael@0: *aCanRedo = false; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::BeginTransaction() michael@0: { michael@0: BeginUpdateViewBatch(); michael@0: michael@0: if (mTxnMgr) { michael@0: mTxnMgr->BeginBatch(nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::EndTransaction() michael@0: { michael@0: if (mTxnMgr) { michael@0: mTxnMgr->EndBatch(false); michael@0: } michael@0: michael@0: EndUpdateViewBatch(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // These two routines are similar to the above, but do not use michael@0: // the transaction managers batching feature. Instead we use michael@0: // a placeholder transaction to wrap up any further transaction michael@0: // while the batch is open. The advantage of this is that michael@0: // placeholder transactions can later merge, if needed. Merging michael@0: // is unavailable between transaction manager batches. michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName) michael@0: { michael@0: NS_PRECONDITION(mPlaceHolderBatch >= 0, "negative placeholder batch count!"); michael@0: if (!mPlaceHolderBatch) michael@0: { michael@0: // time to turn on the batch michael@0: BeginUpdateViewBatch(); michael@0: mPlaceHolderTxn = nullptr; michael@0: mPlaceHolderName = aName; michael@0: nsRefPtr selection = GetSelection(); michael@0: if (selection) { michael@0: mSelState = new nsSelectionState(); michael@0: mSelState->SaveSelection(selection); michael@0: } michael@0: } michael@0: mPlaceHolderBatch++; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::EndPlaceHolderTransaction() michael@0: { michael@0: NS_PRECONDITION(mPlaceHolderBatch > 0, "zero or negative placeholder batch count when ending batch!"); michael@0: if (mPlaceHolderBatch == 1) michael@0: { michael@0: nsCOMPtrselection; michael@0: GetSelection(getter_AddRefs(selection)); michael@0: michael@0: nsCOMPtrselPrivate(do_QueryInterface(selection)); michael@0: michael@0: // By making the assumption that no reflow happens during the calls michael@0: // to EndUpdateViewBatch and ScrollSelectionIntoView, we are able to michael@0: // allow the selection to cache a frame offset which is used by the michael@0: // caret drawing code. We only enable this cache here; at other times, michael@0: // we have no way to know whether reflow invalidates it michael@0: // See bugs 35296 and 199412. michael@0: if (selPrivate) { michael@0: selPrivate->SetCanCacheFrameOffset(true); michael@0: } michael@0: michael@0: { michael@0: // Hide the caret here to avoid hiding it twice, once in EndUpdateViewBatch michael@0: // and once in ScrollSelectionIntoView. michael@0: nsRefPtr caret; michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: michael@0: if (presShell) michael@0: caret = presShell->GetCaret(); michael@0: michael@0: // time to turn off the batch michael@0: EndUpdateViewBatch(); michael@0: // make sure selection is in view michael@0: michael@0: // After ScrollSelectionIntoView(), the pending notifications might be michael@0: // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. michael@0: ScrollSelectionIntoView(false); michael@0: } michael@0: michael@0: // cached for frame offset are Not available now michael@0: if (selPrivate) { michael@0: selPrivate->SetCanCacheFrameOffset(false); michael@0: } michael@0: michael@0: if (mSelState) michael@0: { michael@0: // we saved the selection state, but never got to hand it to placeholder michael@0: // (else we ould have nulled out this pointer), so destroy it to prevent leaks. michael@0: delete mSelState; michael@0: mSelState = nullptr; michael@0: } michael@0: if (mPlaceHolderTxn) // we might have never made a placeholder if no action took place michael@0: { michael@0: nsCOMPtr plcTxn = do_QueryReferent(mPlaceHolderTxn); michael@0: if (plcTxn) michael@0: { michael@0: plcTxn->EndPlaceHolderBatch(); michael@0: } michael@0: else michael@0: { michael@0: // in the future we will check to make sure undo is off here, michael@0: // since that is the only known case where the placeholdertxn would disappear on us. michael@0: // For now just removing the assert. michael@0: } michael@0: // notify editor observers of action but if composing, it's done by michael@0: // text event handler. michael@0: if (!mComposition) { michael@0: NotifyEditorObservers(); michael@0: } michael@0: } michael@0: } michael@0: mPlaceHolderBatch--; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::ShouldTxnSetSelection(bool *aResult) michael@0: { michael@0: NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER); michael@0: *aResult = mShouldTxnSetSelection; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetShouldTxnSetSelection(bool aShould) michael@0: { michael@0: mShouldTxnSetSelection = aShould; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetDocumentIsEmpty(bool *aDocumentIsEmpty) michael@0: { michael@0: *aDocumentIsEmpty = true; michael@0: michael@0: dom::Element* root = GetRoot(); michael@0: NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aDocumentIsEmpty = !root->HasChildren(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // XXX: the rule system should tell us which node to select all on (ie, the root, or the body) michael@0: NS_IMETHODIMP nsEditor::SelectAll() michael@0: { michael@0: if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } michael@0: ForceCompositionEnd(); michael@0: michael@0: nsCOMPtr selCon; michael@0: GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr selection; michael@0: nsresult result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: if (NS_SUCCEEDED(result) && selection) michael@0: { michael@0: result = SelectEntireDocument(selection); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::BeginningOfDocument() michael@0: { michael@0: if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // get the selection michael@0: nsCOMPtr selection; michael@0: nsresult result = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // get the root element michael@0: dom::Element* rootElement = GetRoot(); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: // find first editable thingy michael@0: nsCOMPtr firstNode = GetFirstEditableNode(rootElement); michael@0: if (!firstNode) { michael@0: // just the root node, set selection to inside the root michael@0: return selection->CollapseNative(rootElement, 0); michael@0: } michael@0: michael@0: if (firstNode->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: // If firstNode is text, set selection to beginning of the text node. michael@0: return selection->CollapseNative(firstNode, 0); michael@0: } michael@0: michael@0: // Otherwise, it's a leaf node and we set the selection just in front of it. michael@0: nsCOMPtr parent = firstNode->GetParent(); michael@0: if (!parent) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: int32_t offsetInParent = parent->IndexOf(firstNode); michael@0: return selection->CollapseNative(parent, offsetInParent); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::EndOfDocument() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // get selection michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // get the root element michael@0: nsINode* node = GetRoot(); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: nsINode* child = node->GetLastChild(); michael@0: michael@0: while (child && IsContainer(child->AsDOMNode())) { michael@0: node = child; michael@0: child = node->GetLastChild(); michael@0: } michael@0: michael@0: uint32_t length = node->Length(); michael@0: return selection->CollapseNative(node, int32_t(length)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetDocumentModified(bool *outDocModified) michael@0: { michael@0: NS_ENSURE_TRUE(outDocModified, NS_ERROR_NULL_POINTER); michael@0: michael@0: int32_t modCount = 0; michael@0: GetModificationCount(&modCount); michael@0: michael@0: *outDocModified = (modCount != 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetDocumentCharacterSet(nsACString &characterSet) michael@0: { michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: michael@0: characterSet = doc->GetDocumentCharacterSet(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetDocumentCharacterSet(const nsACString& characterSet) michael@0: { michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: michael@0: doc->SetDocumentCharacterSet(characterSet); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Cut() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CanCut(bool *aCanCut) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Copy() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CanCopy(bool *aCanCut) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::Paste(int32_t aSelectionType) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::PasteTransferable(nsITransferable *aTransferable) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SetAttribute(nsIDOMElement *aElement, const nsAString & aAttribute, const nsAString & aValue) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForSetAttribute(aElement, aAttribute, aValue, michael@0: getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) { michael@0: result = DoTransaction(txn); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetAttributeValue(nsIDOMElement *aElement, michael@0: const nsAString & aAttribute, michael@0: nsAString & aResultValue, michael@0: bool *aResultIsSet) michael@0: { michael@0: NS_ENSURE_TRUE(aResultIsSet, NS_ERROR_NULL_POINTER); michael@0: *aResultIsSet = false; michael@0: if (!aElement) { michael@0: return NS_OK; michael@0: } michael@0: nsAutoString value; michael@0: nsresult rv = aElement->GetAttribute(aAttribute, value); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!DOMStringIsNull(value)) { michael@0: *aResultIsSet = true; michael@0: aResultValue = value; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::RemoveAttribute(nsIDOMElement *aElement, const nsAString& aAttribute) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForRemoveAttribute(aElement, aAttribute, michael@0: getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) { michael@0: result = DoTransaction(txn); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsEditor::OutputsMozDirty() michael@0: { michael@0: // Return true for Composer (!eEditorAllowInteraction) or mail michael@0: // (eEditorMailMask), but false for webpages. michael@0: return !(mFlags & nsIPlaintextEditor::eEditorAllowInteraction) || michael@0: (mFlags & nsIPlaintextEditor::eEditorMailMask); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::MarkNodeDirty(nsIDOMNode* aNode) michael@0: { michael@0: // Mark the node dirty, but not for webpages (bug 599983) michael@0: if (!OutputsMozDirty()) { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: if (element) { michael@0: element->SetAttr(kNameSpaceID_None, nsEditProperty::mozdirty, michael@0: EmptyString(), false); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::GetInlineSpellChecker(bool autoCreate, michael@0: nsIInlineSpellChecker ** aInlineSpellChecker) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aInlineSpellChecker); michael@0: michael@0: if (mDidPreDestroy) { michael@0: // Don't allow people to get or create the spell checker once the editor michael@0: // is going away. michael@0: *aInlineSpellChecker = nullptr; michael@0: return autoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK; michael@0: } michael@0: michael@0: // We don't want to show the spell checking UI if there are no spell check dictionaries available. michael@0: bool canSpell = mozInlineSpellChecker::CanEnableInlineSpellChecking(); michael@0: if (!canSpell) { michael@0: *aInlineSpellChecker = nullptr; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (!mInlineSpellChecker && autoCreate) { michael@0: mInlineSpellChecker = do_CreateInstance(MOZ_INLINESPELLCHECKER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (mInlineSpellChecker) { michael@0: rv = mInlineSpellChecker->Init(this); michael@0: if (NS_FAILED(rv)) michael@0: mInlineSpellChecker = nullptr; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: NS_IF_ADDREF(*aInlineSpellChecker = mInlineSpellChecker); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::Observe(nsISupports* aSubj, const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: NS_ASSERTION(!strcmp(aTopic, michael@0: SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION), michael@0: "Unexpected observer topic"); michael@0: michael@0: // When mozInlineSpellChecker::CanEnableInlineSpellChecking changes michael@0: SyncRealTimeSpell(); michael@0: michael@0: // When nsIEditorSpellCheck::GetCurrentDictionary changes michael@0: if (mInlineSpellChecker) { michael@0: // if the current dictionary is no longer available, find another one michael@0: nsCOMPtr editorSpellCheck; michael@0: mInlineSpellChecker->GetSpellChecker(getter_AddRefs(editorSpellCheck)); michael@0: if (editorSpellCheck) { michael@0: // Note: This might change the current dictionary, which may call michael@0: // this observer recursively. michael@0: editorSpellCheck->CheckCurrentDictionary(); michael@0: } michael@0: michael@0: // update the inline spell checker to reflect the new current dictionary michael@0: mInlineSpellChecker->SpellCheckRange(nullptr); // causes recheck michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::SyncRealTimeSpell() michael@0: { michael@0: bool enable = GetDesiredSpellCheckState(); michael@0: michael@0: // Initializes mInlineSpellChecker michael@0: nsCOMPtr spellChecker; michael@0: GetInlineSpellChecker(enable, getter_AddRefs(spellChecker)); michael@0: michael@0: if (mInlineSpellChecker) { michael@0: // We might have a mInlineSpellChecker even if there are no dictionaries michael@0: // available since we don't destroy the mInlineSpellChecker when the last michael@0: // dictionariy is removed, but in that case spellChecker is null michael@0: mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::SetSpellcheckUserOverride(bool enable) michael@0: { michael@0: mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse; michael@0: michael@0: return SyncRealTimeSpell(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag, michael@0: nsIDOMNode * aParent, michael@0: int32_t aPosition, michael@0: nsIDOMNode ** aNewNode) michael@0: { michael@0: int32_t i; michael@0: michael@0: nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillCreateNode(aTag, aParent, aPosition); michael@0: michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition, michael@0: getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: result = DoTransaction(txn); michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: result = txn->GetNewNode(aNewNode); michael@0: NS_ASSERTION((NS_SUCCEEDED(result)), "GetNewNode can't fail if txn::DoTransaction succeeded."); michael@0: } michael@0: } michael@0: michael@0: mRangeUpdater.SelAdjCreateNode(aParent, aPosition); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidCreateNode(aTag, *aNewNode, aParent, aPosition, result); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode, michael@0: nsIDOMNode * aParent, michael@0: int32_t aPosition) michael@0: { michael@0: int32_t i; michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillInsertNode(aNode, aParent, aPosition); michael@0: michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForInsertElement(aNode, aParent, aPosition, michael@0: getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) { michael@0: result = DoTransaction(txn); michael@0: } michael@0: michael@0: mRangeUpdater.SelAdjInsertNode(aParent, aPosition); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidInsertNode(aNode, aParent, aPosition, result); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::SplitNode(nsIDOMNode * aNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode **aNewLeftNode) michael@0: { michael@0: int32_t i; michael@0: nsAutoRules beginRulesSniffing(this, EditAction::splitNode, nsIEditor::eNext); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillSplitNode(aNode, aOffset); michael@0: michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForSplitNode(aNode, aOffset, getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: result = DoTransaction(txn); michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: result = txn->GetNewNode(aNewLeftNode); michael@0: NS_ASSERTION((NS_SUCCEEDED(result)), "result must succeeded for GetNewNode"); michael@0: } michael@0: } michael@0: michael@0: mRangeUpdater.SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: { michael@0: nsIDOMNode *ptr = *aNewLeftNode; michael@0: mActionListeners[i]->DidSplitNode(aNode, aOffset, ptr, result); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::JoinNodes(nsINode* aNodeToKeep, nsIContent* aNodeToMove) michael@0: { michael@0: // We don't really need aNodeToMove's parent to be non-null -- we could just michael@0: // skip adjusting any ranges in aNodeToMove's parent if there is none. But michael@0: // the current implementation requires it. michael@0: MOZ_ASSERT(aNodeToKeep && aNodeToMove && aNodeToMove->GetParentNode()); michael@0: nsresult res = JoinNodes(aNodeToKeep->AsDOMNode(), aNodeToMove->AsDOMNode(), michael@0: aNodeToMove->GetParentNode()->AsDOMNode()); michael@0: NS_ASSERTION(NS_SUCCEEDED(res), "JoinNodes failed"); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::JoinNodes(nsIDOMNode * aLeftNode, michael@0: nsIDOMNode * aRightNode, michael@0: nsIDOMNode * aParent) michael@0: { michael@0: int32_t i; michael@0: nsAutoRules beginRulesSniffing(this, EditAction::joinNode, nsIEditor::ePrevious); michael@0: michael@0: // remember some values; later used for saved selection updating. michael@0: // find the offset between the nodes to be joined. michael@0: int32_t offset = GetChildOffset(aRightNode, aParent); michael@0: // find the number of children of the lefthand node michael@0: uint32_t oldLeftNodeLen; michael@0: nsresult result = GetLengthOfDOMNode(aLeftNode, oldLeftNodeLen); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillJoinNodes(aLeftNode, aRightNode, aParent); michael@0: michael@0: nsRefPtr txn; michael@0: result = CreateTxnForJoinNode(aLeftNode, aRightNode, getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(result)) { michael@0: result = DoTransaction(txn); michael@0: } michael@0: michael@0: mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (int32_t)oldLeftNodeLen); michael@0: michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidJoinNodes(aLeftNode, aRightNode, aParent, result); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DeleteNode(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_STATE(node); michael@0: return DeleteNode(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::DeleteNode(nsINode* aNode) michael@0: { michael@0: nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::ePrevious); michael@0: michael@0: // save node location for selection updating code. michael@0: for (int32_t i = 0; i < mActionListeners.Count(); i++) { michael@0: mActionListeners[i]->WillDeleteNode(aNode->AsDOMNode()); michael@0: } michael@0: michael@0: nsRefPtr txn; michael@0: nsresult res = CreateTxnForDeleteNode(aNode, getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(res)) { michael@0: res = DoTransaction(txn); michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mActionListeners.Count(); i++) { michael@0: mActionListeners[i]->DidDeleteNode(aNode->AsDOMNode(), res); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ReplaceContainer: replace inNode with a new node (outNode) which is contructed michael@0: // to be of type aNodeType. Put inNodes children into outNode. michael@0: // Callers responsibility to make sure inNode's children can michael@0: // go in outNode. michael@0: nsresult michael@0: nsEditor::ReplaceContainer(nsIDOMNode *inNode, michael@0: nsCOMPtr *outNode, michael@0: const nsAString &aNodeType, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue, michael@0: bool aCloneAttributes) michael@0: { michael@0: NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: nsCOMPtr element; michael@0: nsresult rv = ReplaceContainer(node, getter_AddRefs(element), aNodeType, michael@0: aAttribute, aValue, aCloneAttributes); michael@0: *outNode = element ? element->AsDOMNode() : nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::ReplaceContainer(nsINode* aNode, michael@0: dom::Element** outNode, michael@0: const nsAString& aNodeType, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue, michael@0: bool aCloneAttributes) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: MOZ_ASSERT(outNode); michael@0: michael@0: *outNode = nullptr; michael@0: michael@0: nsCOMPtr parent = aNode->GetParent(); michael@0: NS_ENSURE_STATE(parent); michael@0: michael@0: int32_t offset = parent->IndexOf(aNode); michael@0: michael@0: // create new container michael@0: //new call to use instead to get proper HTML element, bug# 39919 michael@0: nsresult res = CreateHTMLContent(aNodeType, outNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr elem = do_QueryInterface(*outNode); michael@0: michael@0: nsIDOMNode* inNode = aNode->AsDOMNode(); michael@0: michael@0: // set attribute if needed michael@0: if (aAttribute && aValue && !aAttribute->IsEmpty()) { michael@0: res = elem->SetAttribute(*aAttribute, *aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: if (aCloneAttributes) { michael@0: res = CloneAttributes(elem, inNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // notify our internal selection state listener michael@0: // (Note: A nsAutoSelectionReset object must be created michael@0: // before calling this to initialize mRangeUpdater) michael@0: nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, inNode, elem); michael@0: { michael@0: nsAutoTxnsConserveSelection conserveSelection(this); michael@0: while (aNode->HasChildren()) { michael@0: nsCOMPtr child = aNode->GetFirstChild()->AsDOMNode(); michael@0: michael@0: res = DeleteNode(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = InsertNode(child, elem, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // insert new container into tree michael@0: res = InsertNode(elem, parent->AsDOMNode(), offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // delete old container michael@0: return DeleteNode(inNode); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // RemoveContainer: remove inNode, reparenting its children into their michael@0: // the parent of inNode michael@0: // michael@0: nsresult michael@0: nsEditor::RemoveContainer(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return RemoveContainer(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::RemoveContainer(nsINode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr parent = aNode->GetParentNode(); michael@0: NS_ENSURE_STATE(parent); michael@0: michael@0: int32_t offset = parent->IndexOf(aNode); michael@0: michael@0: // loop through the child nodes of inNode and promote them michael@0: // into inNode's parent. michael@0: uint32_t nodeOrigLen = aNode->GetChildCount(); michael@0: michael@0: // notify our internal selection state listener michael@0: nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent, offset, nodeOrigLen); michael@0: michael@0: while (aNode->HasChildren()) { michael@0: nsCOMPtr child = aNode->GetLastChild(); michael@0: nsresult rv = DeleteNode(child->AsDOMNode()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = InsertNode(child->AsDOMNode(), parent->AsDOMNode(), offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return DeleteNode(aNode->AsDOMNode()); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // InsertContainerAbove: insert a new parent for inNode, returned in outNode, michael@0: // which is contructed to be of type aNodeType. outNode becomes michael@0: // a child of inNode's earlier parent. michael@0: // Callers responsibility to make sure inNode's can be child michael@0: // of outNode, and outNode can be child of old parent. michael@0: nsresult michael@0: nsEditor::InsertContainerAbove( nsIDOMNode *inNode, michael@0: nsCOMPtr *outNode, michael@0: const nsAString &aNodeType, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue) michael@0: { michael@0: NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: nsCOMPtr element; michael@0: nsresult rv = InsertContainerAbove(node, getter_AddRefs(element), aNodeType, michael@0: aAttribute, aValue); michael@0: *outNode = element ? element->AsDOMNode() : nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::InsertContainerAbove(nsIContent* aNode, michael@0: dom::Element** aOutNode, michael@0: const nsAString& aNodeType, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: nsCOMPtr parent = aNode->GetParent(); michael@0: NS_ENSURE_STATE(parent); michael@0: int32_t offset = parent->IndexOf(aNode); michael@0: michael@0: // create new container michael@0: nsCOMPtr newContent; michael@0: michael@0: //new call to use instead to get proper HTML element, bug# 39919 michael@0: nsresult res = CreateHTMLContent(aNodeType, getter_AddRefs(newContent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // set attribute if needed michael@0: if (aAttribute && aValue && !aAttribute->IsEmpty()) { michael@0: nsIDOMNode* elem = newContent->AsDOMNode(); michael@0: res = static_cast(elem)->SetAttribute(*aAttribute, *aValue); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // notify our internal selection state listener michael@0: nsAutoInsertContainerSelNotify selNotify(mRangeUpdater); michael@0: michael@0: // put inNode in new parent, outNode michael@0: res = DeleteNode(aNode->AsDOMNode()); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: { michael@0: nsAutoTxnsConserveSelection conserveSelection(this); michael@0: res = InsertNode(aNode->AsDOMNode(), newContent->AsDOMNode(), 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // put new parent in doc michael@0: res = InsertNode(newContent->AsDOMNode(), parent->AsDOMNode(), offset); michael@0: newContent.forget(aOutNode); michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // MoveNode: move aNode to {aParent,aOffset} michael@0: nsresult michael@0: nsEditor::MoveNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aOffset) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: NS_ENSURE_STATE(parent); michael@0: michael@0: return MoveNode(node, parent, aOffset); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: MOZ_ASSERT(aParent); michael@0: MOZ_ASSERT(aOffset == -1 || michael@0: (0 <= aOffset && SafeCast(aOffset) <= aParent->Length())); michael@0: michael@0: int32_t oldOffset; michael@0: nsCOMPtr oldParent = GetNodeLocation(aNode, &oldOffset); michael@0: michael@0: if (aOffset == -1) { michael@0: // Magic value meaning "move to end of aParent". michael@0: aOffset = SafeCast(aParent->Length()); michael@0: } michael@0: michael@0: // Don't do anything if it's already in right place. michael@0: if (aParent == oldParent && aOffset == oldOffset) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Notify our internal selection state listener. michael@0: nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset, michael@0: aParent, aOffset); michael@0: michael@0: // Need to adjust aOffset if we are moving aNode further along in its current michael@0: // parent. michael@0: if (aParent == oldParent && oldOffset < aOffset) { michael@0: // This is because when we delete aNode, it will make the offsets after it michael@0: // off by one. michael@0: aOffset--; michael@0: } michael@0: michael@0: // Hold a reference so aNode doesn't go away when we remove it (bug 772282). michael@0: nsCOMPtr kungFuDeathGrip = aNode; michael@0: michael@0: nsresult rv = DeleteNode(aNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return InsertNode(aNode->AsDOMNode(), aParent->AsDOMNode(), aOffset); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::AddEditorObserver(nsIEditorObserver *aObserver) michael@0: { michael@0: // we don't keep ownership of the observers. They must michael@0: // remove themselves as observers before they are destroyed. michael@0: michael@0: NS_ENSURE_TRUE(aObserver, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Make sure the listener isn't already on the list michael@0: if (mEditorObservers.IndexOf(aObserver) == -1) michael@0: { michael@0: if (!mEditorObservers.AppendObject(aObserver)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::RemoveEditorObserver(nsIEditorObserver *aObserver) michael@0: { michael@0: NS_ENSURE_TRUE(aObserver, NS_ERROR_FAILURE); michael@0: michael@0: if (!mEditorObservers.RemoveObject(aObserver)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class EditorInputEventDispatcher : public nsRunnable michael@0: { michael@0: public: michael@0: EditorInputEventDispatcher(nsEditor* aEditor, michael@0: nsIContent* aTarget, michael@0: bool aIsComposing) michael@0: : mEditor(aEditor) michael@0: , mTarget(aTarget) michael@0: , mIsComposing(aIsComposing) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // Note that we don't need to check mDispatchInputEvent here. We need michael@0: // to check it only when the editor requests to dispatch the input event. michael@0: michael@0: if (!mTarget->IsInDoc()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr ps = mEditor->GetPresShell(); michael@0: if (!ps) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr widget = mEditor->GetWidget(); michael@0: if (!widget) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Even if the change is caused by untrusted event, we need to dispatch michael@0: // trusted input event since it's a fact. michael@0: InternalEditorInputEvent inputEvent(true, NS_EDITOR_INPUT, widget); michael@0: inputEvent.time = static_cast(PR_Now() / 1000); michael@0: inputEvent.mIsComposing = mIsComposing; michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: nsresult rv = michael@0: ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mEditor; michael@0: nsCOMPtr mTarget; michael@0: bool mIsComposing; michael@0: }; michael@0: michael@0: void nsEditor::NotifyEditorObservers(void) michael@0: { michael@0: for (int32_t i = 0; i < mEditorObservers.Count(); i++) { michael@0: mEditorObservers[i]->EditAction(); michael@0: } michael@0: michael@0: if (!mDispatchInputEvent) { michael@0: return; michael@0: } michael@0: michael@0: FireInputEvent(); michael@0: } michael@0: michael@0: void michael@0: nsEditor::FireInputEvent() michael@0: { michael@0: // We don't need to dispatch multiple input events if there is a pending michael@0: // input event. However, it may have different event target. If we resolved michael@0: // this issue, we need to manage the pending events in an array. But it's michael@0: // overwork. We don't need to do it for the very rare case. michael@0: michael@0: nsCOMPtr target = GetInputEventTargetContent(); michael@0: NS_ENSURE_TRUE_VOID(target); michael@0: michael@0: // NOTE: Don't refer IsIMEComposing() because it returns false even before michael@0: // compositionend. However, DOM Level 3 Events defines it should be michael@0: // true after compositionstart and before compositionend. michael@0: nsContentUtils::AddScriptRunner( michael@0: new EditorInputEventDispatcher(this, target, !!GetComposition())); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::AddEditActionListener(nsIEditActionListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Make sure the listener isn't already on the list michael@0: if (mActionListeners.IndexOf(aListener) == -1) michael@0: { michael@0: if (!mActionListeners.AppendObject(aListener)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::RemoveEditActionListener(nsIEditActionListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE); michael@0: michael@0: if (!mActionListeners.RemoveObject(aListener)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::AddDocumentStateListener(nsIDocumentStateListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (mDocStateListeners.IndexOf(aListener) == -1) michael@0: { michael@0: if (!mDocStateListeners.AppendObject(aListener)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::RemoveDocumentStateListener(nsIDocumentStateListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (!mDocStateListeners.RemoveObject(aListener)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::OutputToString(const nsAString& aFormatType, michael@0: uint32_t aFlags, michael@0: nsAString& aOutputString) michael@0: { michael@0: // these should be implemented by derived classes. michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::OutputToStream(nsIOutputStream* aOutputStream, michael@0: const nsAString& aFormatType, michael@0: const nsACString& aCharsetOverride, michael@0: uint32_t aFlags) michael@0: { michael@0: // these should be implemented by derived classes. michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DumpContentTree() michael@0: { michael@0: #ifdef DEBUG michael@0: if (mRootElement) { michael@0: mRootElement->List(stdout); michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DebugDumpContent() michael@0: { michael@0: #ifdef DEBUG michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtrbodyElem; michael@0: doc->GetBody(getter_AddRefs(bodyElem)); michael@0: nsCOMPtr content = do_QueryInterface(bodyElem); michael@0: if (content) michael@0: content->List(); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DebugUnitTests(int32_t *outNumTests, int32_t *outNumTestsFailed) michael@0: { michael@0: #ifdef DEBUG michael@0: NS_NOTREACHED("This should never get called. Overridden by subclasses"); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsEditor::ArePreservingSelection() michael@0: { michael@0: return !(mSavedSel.IsEmpty()); michael@0: } michael@0: michael@0: void michael@0: nsEditor::PreserveSelectionAcrossActions(Selection* aSel) michael@0: { michael@0: mSavedSel.SaveSelection(aSel); michael@0: mRangeUpdater.RegisterSelectionState(mSavedSel); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::RestorePreservedSelection(nsISelection *aSel) michael@0: { michael@0: if (mSavedSel.IsEmpty()) return NS_ERROR_FAILURE; michael@0: mSavedSel.RestoreSelection(aSel); michael@0: StopPreservingSelection(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsEditor::StopPreservingSelection() michael@0: { michael@0: mRangeUpdater.DropSelectionState(mSavedSel); michael@0: mSavedSel.MakeEmpty(); michael@0: } michael@0: michael@0: void michael@0: nsEditor::EnsureComposition(mozilla::WidgetGUIEvent* aEvent) michael@0: { michael@0: if (mComposition) { michael@0: return; michael@0: } michael@0: // The compositionstart event must cause creating new TextComposition michael@0: // instance at being dispatched by IMEStateManager. michael@0: mComposition = IMEStateManager::GetTextCompositionFor(aEvent); michael@0: if (!mComposition) { michael@0: MOZ_CRASH("IMEStateManager doesn't return proper composition"); michael@0: } michael@0: mComposition->StartHandlingComposition(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::BeginIMEComposition(WidgetCompositionEvent* aCompositionEvent) michael@0: { michael@0: MOZ_ASSERT(!mComposition, "There is composition already"); michael@0: EnsureComposition(aCompositionEvent); michael@0: if (mPhonetic) { michael@0: mPhonetic->Truncate(0); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsEditor::EndIMEComposition() michael@0: { michael@0: NS_ENSURE_TRUE_VOID(mComposition); // nothing to do michael@0: michael@0: // commit the IME transaction..we can get at it via the transaction mgr. michael@0: // Note that this means IME won't work without an undo stack! michael@0: if (mTxnMgr) { michael@0: nsCOMPtr txn = mTxnMgr->PeekUndoStack(); michael@0: nsCOMPtr plcTxn = do_QueryInterface(txn); michael@0: if (plcTxn) { michael@0: DebugOnly rv = plcTxn->Commit(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), michael@0: "nsIAbsorbingTransaction::Commit() failed"); michael@0: } michael@0: } michael@0: michael@0: /* reset the data we need to construct a transaction */ michael@0: mIMETextNode = nullptr; michael@0: mIMETextOffset = 0; michael@0: mComposition->EndHandlingComposition(this); michael@0: mComposition = nullptr; michael@0: michael@0: // notify editor observers of action michael@0: NotifyEditorObservers(); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetPhonetic(nsAString& aPhonetic) michael@0: { michael@0: if (mPhonetic) michael@0: aPhonetic = *mPhonetic; michael@0: else michael@0: aPhonetic.Truncate(0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::ForceCompositionEnd() michael@0: { michael@0: nsCOMPtr ps = GetPresShell(); michael@0: if (!ps) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: nsPresContext* pc = ps->GetPresContext(); michael@0: if (!pc) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mComposition) { michael@0: // XXXmnakano see bug 558976, ResetInputState() has two meaning which are michael@0: // "commit the composition" and "cursor is moved". This method name is michael@0: // "ForceCompositionEnd", so, ResetInputState() should be used only for the michael@0: // former here. However, ResetInputState() is also used for the latter here michael@0: // because even if we don't have composition, we call ResetInputState() on michael@0: // Linux. Currently, nsGtkIMModule can know the timing of the cursor move, michael@0: // so, the latter meaning should be gone. michael@0: // XXX This may commit a composition in another editor. michael@0: return IMEStateManager::NotifyIME(NOTIFY_IME_OF_CURSOR_POS_CHANGED, pc); michael@0: } michael@0: michael@0: return IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, pc); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetPreferredIMEState(IMEState *aState) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aState); michael@0: aState->mEnabled = IMEState::ENABLED; michael@0: aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE; michael@0: michael@0: if (IsReadonly() || IsDisabled()) { michael@0: aState->mEnabled = IMEState::DISABLED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr content = GetRoot(); michael@0: NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); michael@0: michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: switch (frame->StyleUIReset()->mIMEMode) { michael@0: case NS_STYLE_IME_MODE_AUTO: michael@0: if (IsPasswordEditor()) michael@0: aState->mEnabled = IMEState::PASSWORD; michael@0: break; michael@0: case NS_STYLE_IME_MODE_DISABLED: michael@0: // we should use password state for |ime-mode: disabled;|. michael@0: aState->mEnabled = IMEState::PASSWORD; michael@0: break; michael@0: case NS_STYLE_IME_MODE_ACTIVE: michael@0: aState->mOpen = IMEState::OPEN; michael@0: break; michael@0: case NS_STYLE_IME_MODE_INACTIVE: michael@0: aState->mOpen = IMEState::CLOSED; michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetComposing(bool* aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = IsIMEComposing(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* Non-interface, public methods */ michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::GetRootElement(nsIDOMElement **aRootElement) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRootElement); michael@0: NS_ENSURE_TRUE(mRootElement, NS_ERROR_NOT_AVAILABLE); michael@0: nsCOMPtr rootElement = do_QueryInterface(mRootElement); michael@0: rootElement.forget(aRootElement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be prefaced michael@0: * with a call to StartOperation, naming the action and direction */ michael@0: NS_IMETHODIMP michael@0: nsEditor::StartOperation(EditAction opID, nsIEditor::EDirection aDirection) michael@0: { michael@0: mAction = opID; michael@0: mDirection = aDirection; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be followed michael@0: * with a call to EndOperation */ michael@0: NS_IMETHODIMP michael@0: nsEditor::EndOperation() michael@0: { michael@0: mAction = EditAction::none; michael@0: mDirection = eNone; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CloneAttribute(const nsAString & aAttribute, michael@0: nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode) michael@0: { michael@0: NS_ENSURE_TRUE(aDestNode && aSourceNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr destElement = do_QueryInterface(aDestNode); michael@0: nsCOMPtr sourceElement = do_QueryInterface(aSourceNode); michael@0: NS_ENSURE_TRUE(destElement && sourceElement, NS_ERROR_NO_INTERFACE); michael@0: michael@0: nsAutoString attrValue; michael@0: bool isAttrSet; michael@0: nsresult rv = GetAttributeValue(sourceElement, michael@0: aAttribute, michael@0: attrValue, michael@0: &isAttrSet); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (isAttrSet) michael@0: rv = SetAttribute(destElement, aAttribute, attrValue); michael@0: else michael@0: rv = RemoveAttribute(destElement, aAttribute); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Objects must be DOM elements michael@0: NS_IMETHODIMP michael@0: nsEditor::CloneAttributes(nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode) michael@0: { michael@0: NS_ENSURE_TRUE(aDestNode && aSourceNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr destElement = do_QueryInterface(aDestNode); michael@0: nsCOMPtr sourceElement = do_QueryInterface(aSourceNode); michael@0: NS_ENSURE_TRUE(destElement && sourceElement, NS_ERROR_NO_INTERFACE); michael@0: michael@0: nsCOMPtr sourceAttributes; michael@0: sourceElement->GetAttributes(getter_AddRefs(sourceAttributes)); michael@0: nsCOMPtr destAttributes; michael@0: destElement->GetAttributes(getter_AddRefs(destAttributes)); michael@0: NS_ENSURE_TRUE(sourceAttributes && destAttributes, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: michael@0: // Use transaction system for undo only if destination michael@0: // is already in the document michael@0: nsCOMPtr p = aDestNode; michael@0: nsCOMPtr rootNode = do_QueryInterface(GetRoot()); michael@0: NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER); michael@0: bool destInBody = true; michael@0: while (p && p != rootNode) michael@0: { michael@0: nsCOMPtr tmp; michael@0: if (NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) michael@0: { michael@0: destInBody = false; michael@0: break; michael@0: } michael@0: p = tmp; michael@0: } michael@0: michael@0: uint32_t sourceCount; michael@0: sourceAttributes->GetLength(&sourceCount); michael@0: uint32_t destCount; michael@0: destAttributes->GetLength(&destCount); michael@0: nsCOMPtr attr; michael@0: michael@0: // Clear existing attributes michael@0: for (uint32_t i = 0; i < destCount; i++) { michael@0: // always remove item number 0 (first item in list) michael@0: if (NS_SUCCEEDED(destAttributes->Item(0, getter_AddRefs(attr))) && attr) { michael@0: nsString str; michael@0: if (NS_SUCCEEDED(attr->GetName(str))) { michael@0: if (destInBody) { michael@0: RemoveAttribute(destElement, str); michael@0: } else { michael@0: destElement->RemoveAttribute(str); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult result = NS_OK; michael@0: michael@0: // Set just the attributes that the source element has michael@0: for (uint32_t i = 0; i < sourceCount; i++) michael@0: { michael@0: if (NS_SUCCEEDED(sourceAttributes->Item(i, getter_AddRefs(attr))) && attr) { michael@0: nsString sourceAttrName; michael@0: if (NS_SUCCEEDED(attr->GetName(sourceAttrName))) { michael@0: nsString sourceAttrValue; michael@0: /* Presence of an attribute in the named node map indicates that it was michael@0: * set on the element even if it has no value. michael@0: */ michael@0: if (NS_SUCCEEDED(attr->GetValue(sourceAttrValue))) { michael@0: if (destInBody) { michael@0: result = SetAttributeOrEquivalent(destElement, sourceAttrName, sourceAttrValue, false); michael@0: } else { michael@0: // the element is not inserted in the document yet, we don't want to put a michael@0: // transaction on the UndoStack michael@0: result = SetAttributeOrEquivalent(destElement, sourceAttrName, sourceAttrValue, true); michael@0: } michael@0: } else { michael@0: // Do we ever get here? michael@0: #if DEBUG_cmanske michael@0: printf("Attribute in sourceAttribute has empty value in nsEditor::CloneAttributes()\n"); michael@0: #endif michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::ScrollSelectionIntoView(bool aScrollToAnchor) michael@0: { michael@0: nsCOMPtr selCon; michael@0: if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon) michael@0: { michael@0: int16_t region = nsISelectionController::SELECTION_FOCUS_REGION; michael@0: michael@0: if (aScrollToAnchor) michael@0: region = nsISelectionController::SELECTION_ANCHOR_REGION; michael@0: michael@0: selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, michael@0: region, nsISelectionController::SCROLL_OVERFLOW_HIDDEN); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::InsertTextImpl(const nsAString& aStringToInsert, michael@0: nsCOMPtr* aInOutNode, michael@0: int32_t* aInOutOffset, michael@0: nsIDOMDocument* aDoc) michael@0: { michael@0: // NOTE: caller *must* have already used nsAutoTxnsConserveSelection michael@0: // stack-based class to turn off txn selection updating. Caller also turned michael@0: // on rules sniffing if desired. michael@0: michael@0: NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc, michael@0: NS_ERROR_NULL_POINTER); michael@0: if (!mComposition && aStringToInsert.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr node = do_QueryInterface(*aInOutNode); michael@0: NS_ENSURE_STATE(node); michael@0: uint32_t offset = static_cast(*aInOutOffset); michael@0: michael@0: if (!node->IsNodeOfType(nsINode::eTEXT) && IsPlaintextEditor()) { michael@0: nsCOMPtr root = GetRoot(); michael@0: // In some cases, node is the anonymous DIV, and offset is 0. To avoid michael@0: // injecting unneeded text nodes, we first look to see if we have one michael@0: // available. In that case, we'll just adjust node and offset accordingly. michael@0: if (node == root && offset == 0 && node->HasChildren() && michael@0: node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) { michael@0: node = node->GetFirstChild(); michael@0: } michael@0: // In some other cases, node is the anonymous DIV, and offset points to the michael@0: // terminating mozBR. In that case, we'll adjust aInOutNode and michael@0: // aInOutOffset to the preceding text node, if any. michael@0: if (node == root && offset > 0 && node->GetChildAt(offset - 1) && michael@0: node->GetChildAt(offset - 1)->IsNodeOfType(nsINode::eTEXT)) { michael@0: node = node->GetChildAt(offset - 1); michael@0: offset = node->Length(); michael@0: } michael@0: // Sometimes, node is the mozBR element itself. In that case, we'll adjust michael@0: // the insertion point to the previous text node, if one exists, or to the michael@0: // parent anonymous DIV. michael@0: if (nsTextEditUtils::IsMozBR(node) && offset == 0) { michael@0: if (node->GetPreviousSibling() && michael@0: node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) { michael@0: node = node->GetPreviousSibling(); michael@0: offset = node->Length(); michael@0: } else if (node->GetParentNode() && node->GetParentNode() == root) { michael@0: node = node->GetParentNode(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult res; michael@0: if (mComposition) { michael@0: if (!node->IsNodeOfType(nsINode::eTEXT)) { michael@0: // create a text node michael@0: nsCOMPtr doc = do_QueryInterface(aDoc); michael@0: NS_ENSURE_STATE(doc); michael@0: nsRefPtr newNode = doc->CreateTextNode(EmptyString()); michael@0: // then we insert it into the dom tree michael@0: res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = newNode; michael@0: offset = 0; michael@0: } michael@0: nsCOMPtr charDataNode = do_QueryInterface(node); michael@0: NS_ENSURE_STATE(charDataNode); michael@0: res = InsertTextIntoTextNodeImpl(aStringToInsert, charDataNode, offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: offset += aStringToInsert.Length(); michael@0: } else { michael@0: if (node->IsNodeOfType(nsINode::eTEXT)) { michael@0: // we are inserting text into an existing text node. michael@0: nsCOMPtr charDataNode = do_QueryInterface(node); michael@0: NS_ENSURE_STATE(charDataNode); michael@0: res = InsertTextIntoTextNodeImpl(aStringToInsert, charDataNode, offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: offset += aStringToInsert.Length(); michael@0: } else { michael@0: // we are inserting text into a non-text node. first we have to create a michael@0: // textnode (this also populates it with the text) michael@0: nsCOMPtr doc = do_QueryInterface(aDoc); michael@0: NS_ENSURE_STATE(doc); michael@0: nsRefPtr newNode = doc->CreateTextNode(aStringToInsert); michael@0: // then we insert it into the dom tree michael@0: res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = newNode; michael@0: offset = aStringToInsert.Length(); michael@0: } michael@0: } michael@0: michael@0: *aInOutNode = node->AsDOMNode(); michael@0: *aInOutOffset = static_cast(offset); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert, michael@0: nsIDOMCharacterData *aTextNode, michael@0: int32_t aOffset, michael@0: bool aSuppressIME) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = NS_OK; michael@0: bool isIMETransaction = false; michael@0: // aSuppressIME is used when editor must insert text, yet this text is not michael@0: // part of current ime operation. example: adjusting whitespace around an ime insertion. michael@0: if (mComposition && !aSuppressIME) { michael@0: if (!mIMETextNode) { michael@0: mIMETextNode = aTextNode; michael@0: mIMETextOffset = aOffset; michael@0: } michael@0: // Modify mPhonetic with raw text input clauses. michael@0: const TextRangeArray* ranges = mComposition->GetRanges(); michael@0: for (uint32_t i = 0; i < (ranges ? ranges->Length() : 0); ++i) { michael@0: const TextRange& textRange = ranges->ElementAt(i); michael@0: if (!textRange.Length() || michael@0: textRange.mRangeType != NS_TEXTRANGE_RAWINPUT) { michael@0: continue; michael@0: } michael@0: if (!mPhonetic) { michael@0: mPhonetic = new nsString(); michael@0: } michael@0: nsAutoString stringToInsert(aStringToInsert); michael@0: stringToInsert.Mid(*mPhonetic, michael@0: textRange.mStartOffset, textRange.Length()); michael@0: } michael@0: michael@0: nsRefPtr imeTxn; michael@0: result = CreateTxnForIMEText(aStringToInsert, getter_AddRefs(imeTxn)); michael@0: txn = imeTxn; michael@0: isIMETransaction = true; michael@0: } michael@0: else michael@0: { michael@0: nsRefPtr insertTxn; michael@0: result = CreateTxnForInsertText(aStringToInsert, aTextNode, aOffset, michael@0: getter_AddRefs(insertTxn)); michael@0: txn = insertTxn; michael@0: } michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // let listeners know what's up michael@0: int32_t i; michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillInsertText(aTextNode, aOffset, aStringToInsert); michael@0: michael@0: // XXX we may not need these view batches anymore. This is handled at a higher level now I believe michael@0: BeginUpdateViewBatch(); michael@0: result = DoTransaction(txn); michael@0: EndUpdateViewBatch(); michael@0: michael@0: mRangeUpdater.SelAdjInsertText(aTextNode, aOffset, aStringToInsert); michael@0: michael@0: // let listeners know what happened michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidInsertText(aTextNode, aOffset, aStringToInsert, result); michael@0: michael@0: // Added some cruft here for bug 43366. Layout was crashing because we left an michael@0: // empty text node lying around in the document. So I delete empty text nodes michael@0: // caused by IME. I have to mark the IME transaction as "fixed", which means michael@0: // that furure ime txns won't merge with it. This is because we don't want michael@0: // future ime txns trying to put their text into a node that is no longer in michael@0: // the document. This does not break undo/redo, because all these txns are michael@0: // wrapped in a parent PlaceHolder txn, and placeholder txns are already michael@0: // savvy to having multiple ime txns inside them. michael@0: michael@0: // delete empty ime text node if there is one michael@0: if (isIMETransaction && mIMETextNode) michael@0: { michael@0: uint32_t len; michael@0: mIMETextNode->GetLength(&len); michael@0: if (!len) michael@0: { michael@0: DeleteNode(mIMETextNode); michael@0: mIMETextNode = nullptr; michael@0: static_cast(txn.get())->MarkFixed(); // mark the ime txn "fixed" michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::SelectEntireDocument(nsISelection *aSelection) michael@0: { michael@0: if (!aSelection) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: nsCOMPtr rootElement = do_QueryInterface(GetRoot()); michael@0: if (!rootElement) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: return aSelection->SelectAllChildren(rootElement); michael@0: } michael@0: michael@0: michael@0: nsINode* michael@0: nsEditor::GetFirstEditableNode(nsINode* aRoot) michael@0: { michael@0: MOZ_ASSERT(aRoot); michael@0: michael@0: nsIContent* node = GetLeftmostChild(aRoot); michael@0: if (node && !IsEditable(node)) { michael@0: node = GetNextNode(node, /* aEditableNode = */ true); michael@0: } michael@0: michael@0: return (node != aRoot) ? node : nullptr; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::NotifyDocumentListeners(TDocumentListenerNotification aNotificationType) michael@0: { michael@0: int32_t numListeners = mDocStateListeners.Count(); michael@0: if (!numListeners) // maybe there just aren't any. michael@0: return NS_OK; michael@0: michael@0: nsCOMArray listeners(mDocStateListeners); michael@0: nsresult rv = NS_OK; michael@0: int32_t i; michael@0: michael@0: switch (aNotificationType) michael@0: { michael@0: case eDocumentCreated: michael@0: for (i = 0; i < numListeners;i++) michael@0: { michael@0: rv = listeners[i]->NotifyDocumentCreated(); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case eDocumentToBeDestroyed: michael@0: for (i = 0; i < numListeners;i++) michael@0: { michael@0: rv = listeners[i]->NotifyDocumentWillBeDestroyed(); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case eDocumentStateChanged: michael@0: { michael@0: bool docIsDirty; michael@0: rv = GetDocumentModified(&docIsDirty); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (static_cast(docIsDirty) == mDocDirtyState) michael@0: return NS_OK; michael@0: michael@0: mDocDirtyState = docIsDirty; michael@0: michael@0: for (i = 0; i < numListeners;i++) michael@0: { michael@0: rv = listeners[i]->NotifyDocumentStateChanged(mDocDirtyState); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("Unknown notification"); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateTxnForInsertText(const nsAString & aStringToInsert, michael@0: nsIDOMCharacterData *aTextNode, michael@0: int32_t aOffset, michael@0: InsertTextTxn ** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aTextNode && aTxn, NS_ERROR_NULL_POINTER); michael@0: nsresult rv; michael@0: michael@0: nsRefPtr txn = new InsertTextTxn(); michael@0: rv = txn->Init(aTextNode, aOffset, aStringToInsert, this); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement, michael@0: uint32_t aOffset, michael@0: uint32_t aLength) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = CreateTxnForDeleteText(aElement, aOffset, aLength, michael@0: getter_AddRefs(txn)); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::deleteText, nsIEditor::ePrevious); michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: // let listeners know what's up michael@0: int32_t i; michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillDeleteText(aElement, aOffset, aLength); michael@0: michael@0: result = DoTransaction(txn); michael@0: michael@0: // let listeners know what happened michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidDeleteText(aElement, aOffset, aLength, result); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::CreateTxnForDeleteText(nsIDOMCharacterData* aElement, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: DeleteTextTxn** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new DeleteTextTxn(); michael@0: michael@0: nsresult res = txn->Init(this, aElement, aOffset, aLength, &mRangeUpdater); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: txn.forget(aTxn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateTxnForSplitNode(nsIDOMNode *aNode, michael@0: uint32_t aOffset, michael@0: SplitElementTxn **aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new SplitElementTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aNode, aOffset); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateTxnForJoinNode(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: JoinElementTxn **aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aLeftNode && aRightNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new JoinElementTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aLeftNode, aRightNode); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // END nsEditor core implementation michael@0: michael@0: michael@0: // BEGIN nsEditor public helper methods michael@0: michael@0: nsresult michael@0: nsEditor::SplitNodeImpl(nsIDOMNode * aExistingRightNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode* aNewLeftNode, michael@0: nsIDOMNode* aParent) michael@0: { michael@0: NS_ASSERTION(((nullptr!=aExistingRightNode) && michael@0: (nullptr!=aNewLeftNode) && michael@0: (nullptr!=aParent)), michael@0: "null arg"); michael@0: nsresult result; michael@0: if ((nullptr!=aExistingRightNode) && michael@0: (nullptr!=aNewLeftNode) && michael@0: (nullptr!=aParent)) michael@0: { michael@0: // get selection michael@0: nsCOMPtr selection; michael@0: result = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // remember some selection points michael@0: nsCOMPtr selStartNode, selEndNode; michael@0: int32_t selStartOffset, selEndOffset; michael@0: result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset); michael@0: if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that michael@0: result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset); michael@0: if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that michael@0: michael@0: nsCOMPtr resultNode; michael@0: result = aParent->InsertBefore(aNewLeftNode, aExistingRightNode, getter_AddRefs(resultNode)); michael@0: //printf(" after insert\n"); content->List(); // DEBUG michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: // split the children between the 2 nodes michael@0: // at this point, aExistingRightNode has all the children michael@0: // move all the children whose index is < aOffset to aNewLeftNode michael@0: if (0<=aOffset) // don't bother unless we're going to move at least one child michael@0: { michael@0: // if it's a text node, just shuffle around some text michael@0: nsCOMPtr rightNodeAsText( do_QueryInterface(aExistingRightNode) ); michael@0: nsCOMPtr leftNodeAsText( do_QueryInterface(aNewLeftNode) ); michael@0: if (leftNodeAsText && rightNodeAsText) michael@0: { michael@0: // fix right node michael@0: nsAutoString leftText; michael@0: rightNodeAsText->SubstringData(0, aOffset, leftText); michael@0: rightNodeAsText->DeleteData(0, aOffset); michael@0: // fix left node michael@0: leftNodeAsText->SetData(leftText); michael@0: // moose michael@0: } michael@0: else michael@0: { // otherwise it's an interior node, so shuffle around the children michael@0: // go through list backwards so deletes don't interfere with the iteration michael@0: nsCOMPtr childNodes; michael@0: result = aExistingRightNode->GetChildNodes(getter_AddRefs(childNodes)); michael@0: if ((NS_SUCCEEDED(result)) && (childNodes)) michael@0: { michael@0: int32_t i=aOffset-1; michael@0: for ( ; ((NS_SUCCEEDED(result)) && (0<=i)); i--) michael@0: { michael@0: nsCOMPtr childNode; michael@0: result = childNodes->Item(i, getter_AddRefs(childNode)); michael@0: if ((NS_SUCCEEDED(result)) && (childNode)) michael@0: { michael@0: result = aExistingRightNode->RemoveChild(childNode, getter_AddRefs(resultNode)); michael@0: //printf(" after remove\n"); content->List(); // DEBUG michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: nsCOMPtr firstChild; michael@0: aNewLeftNode->GetFirstChild(getter_AddRefs(firstChild)); michael@0: result = aNewLeftNode->InsertBefore(childNode, firstChild, getter_AddRefs(resultNode)); michael@0: //printf(" after append\n"); content->List(); // DEBUG michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // handle selection michael@0: nsCOMPtr ps = GetPresShell(); michael@0: if (ps) michael@0: ps->FlushPendingNotifications(Flush_Frames); michael@0: michael@0: if (GetShouldTxnSetSelection()) michael@0: { michael@0: // editor wants us to set selection at split point michael@0: selection->Collapse(aNewLeftNode, aOffset); michael@0: } michael@0: else if (selStartNode) michael@0: { michael@0: // else adjust the selection if needed. if selStartNode is null, then there was no selection. michael@0: // HACK: this is overly simplified - multi-range selections need more work than this michael@0: if (selStartNode.get() == aExistingRightNode) michael@0: { michael@0: if (selStartOffset < aOffset) michael@0: { michael@0: selStartNode = aNewLeftNode; michael@0: } michael@0: else michael@0: { michael@0: selStartOffset -= aOffset; michael@0: } michael@0: } michael@0: if (selEndNode.get() == aExistingRightNode) michael@0: { michael@0: if (selEndOffset < aOffset) michael@0: { michael@0: selEndNode = aNewLeftNode; michael@0: } michael@0: else michael@0: { michael@0: selEndOffset -= aOffset; michael@0: } michael@0: } michael@0: selection->Collapse(selStartNode,selStartOffset); michael@0: selection->Extend(selEndNode,selEndOffset); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else michael@0: result = NS_ERROR_INVALID_ARG; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::JoinNodesImpl(nsINode* aNodeToKeep, michael@0: nsINode* aNodeToJoin, michael@0: nsINode* aParent) michael@0: { michael@0: MOZ_ASSERT(aNodeToKeep); michael@0: MOZ_ASSERT(aNodeToJoin); michael@0: MOZ_ASSERT(aParent); michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // remember some selection points michael@0: nsCOMPtr selStartNode; michael@0: int32_t selStartOffset; michael@0: nsresult result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset); michael@0: if (NS_FAILED(result)) { michael@0: selStartNode = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr selEndNode; michael@0: int32_t selEndOffset; michael@0: result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset); michael@0: // Joe or Kin should comment here on why the following line is not a copy/paste error michael@0: if (NS_FAILED(result)) { michael@0: selStartNode = nullptr; michael@0: } michael@0: michael@0: uint32_t firstNodeLength = aNodeToJoin->Length(); michael@0: michael@0: int32_t joinOffset; michael@0: GetNodeLocation(aNodeToJoin, &joinOffset); michael@0: int32_t keepOffset; michael@0: nsINode* parent = GetNodeLocation(aNodeToKeep, &keepOffset); michael@0: michael@0: // if selection endpoint is between the nodes, remember it as being michael@0: // in the one that is going away instead. This simplifies later selection michael@0: // adjustment logic at end of this method. michael@0: if (selStartNode) { michael@0: if (selStartNode == parent && michael@0: joinOffset < selStartOffset && selStartOffset <= keepOffset) { michael@0: selStartNode = aNodeToJoin; michael@0: selStartOffset = firstNodeLength; michael@0: } michael@0: if (selEndNode == parent && michael@0: joinOffset < selEndOffset && selEndOffset <= keepOffset) { michael@0: selEndNode = aNodeToJoin; michael@0: selEndOffset = firstNodeLength; michael@0: } michael@0: } michael@0: michael@0: // ok, ready to do join now. michael@0: // if it's a text node, just shuffle around some text michael@0: nsCOMPtr keepNodeAsText( do_QueryInterface(aNodeToKeep) ); michael@0: nsCOMPtr joinNodeAsText( do_QueryInterface(aNodeToJoin) ); michael@0: if (keepNodeAsText && joinNodeAsText) { michael@0: nsAutoString rightText; michael@0: nsAutoString leftText; michael@0: keepNodeAsText->GetData(rightText); michael@0: joinNodeAsText->GetData(leftText); michael@0: leftText += rightText; michael@0: keepNodeAsText->SetData(leftText); michael@0: } else { michael@0: // otherwise it's an interior node, so shuffle around the children michael@0: nsCOMPtr childNodes = aNodeToJoin->ChildNodes(); michael@0: MOZ_ASSERT(childNodes); michael@0: michael@0: // remember the first child in aNodeToKeep, we'll insert all the children of aNodeToJoin in front of it michael@0: // GetFirstChild returns nullptr firstNode if aNodeToKeep has no children, that's ok. michael@0: nsCOMPtr firstNode = aNodeToKeep->GetFirstChild(); michael@0: michael@0: // have to go through the list backwards to keep deletes from interfering with iteration michael@0: for (uint32_t i = childNodes->Length(); i > 0; --i) { michael@0: nsCOMPtr childNode = childNodes->Item(i - 1); michael@0: if (childNode) { michael@0: // prepend children of aNodeToJoin michael@0: ErrorResult err; michael@0: aNodeToKeep->InsertBefore(*childNode, firstNode, err); michael@0: NS_ENSURE_SUCCESS(err.ErrorCode(), err.ErrorCode()); michael@0: firstNode = childNode.forget(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // delete the extra node michael@0: ErrorResult err; michael@0: aParent->RemoveChild(*aNodeToJoin, err); michael@0: michael@0: if (GetShouldTxnSetSelection()) { michael@0: // editor wants us to set selection at join point michael@0: selection->Collapse(aNodeToKeep, SafeCast(firstNodeLength)); michael@0: } else if (selStartNode) { michael@0: // and adjust the selection if needed michael@0: // HACK: this is overly simplified - multi-range selections need more work than this michael@0: bool bNeedToAdjust = false; michael@0: michael@0: // check to see if we joined nodes where selection starts michael@0: if (selStartNode == aNodeToJoin) { michael@0: bNeedToAdjust = true; michael@0: selStartNode = aNodeToKeep; michael@0: } else if (selStartNode == aNodeToKeep) { michael@0: bNeedToAdjust = true; michael@0: selStartOffset += firstNodeLength; michael@0: } michael@0: michael@0: // check to see if we joined nodes where selection ends michael@0: if (selEndNode == aNodeToJoin) { michael@0: bNeedToAdjust = true; michael@0: selEndNode = aNodeToKeep; michael@0: } else if (selEndNode == aNodeToKeep) { michael@0: bNeedToAdjust = true; michael@0: selEndOffset += firstNodeLength; michael@0: } michael@0: michael@0: // adjust selection if needed michael@0: if (bNeedToAdjust) { michael@0: selection->Collapse(selStartNode, selStartOffset); michael@0: selection->Extend(selEndNode, selEndOffset); michael@0: } michael@0: } michael@0: michael@0: return err.ErrorCode(); michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: nsEditor::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent) michael@0: { michael@0: MOZ_ASSERT(aChild && aParent); michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: nsCOMPtr child = do_QueryInterface(aChild); michael@0: MOZ_ASSERT(parent && child); michael@0: michael@0: int32_t idx = parent->IndexOf(child); michael@0: MOZ_ASSERT(idx != -1); michael@0: return idx; michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: nsEditor::GetNodeLocation(nsIDOMNode* aChild, int32_t* outOffset) michael@0: { michael@0: MOZ_ASSERT(aChild && outOffset); michael@0: NS_ENSURE_TRUE(aChild && outOffset, nullptr); michael@0: *outOffset = -1; michael@0: michael@0: nsCOMPtr parent; michael@0: michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: aChild->GetParentNode(getter_AddRefs(parent)))); michael@0: if (parent) { michael@0: *outOffset = GetChildOffset(aChild, parent); michael@0: } michael@0: michael@0: return parent.forget(); michael@0: } michael@0: michael@0: nsINode* michael@0: nsEditor::GetNodeLocation(nsINode* aChild, int32_t* aOffset) michael@0: { michael@0: MOZ_ASSERT(aChild); michael@0: MOZ_ASSERT(aOffset); michael@0: michael@0: nsINode* parent = aChild->GetParentNode(); michael@0: if (parent) { michael@0: *aOffset = parent->IndexOf(aChild); michael@0: MOZ_ASSERT(*aOffset != -1); michael@0: } else { michael@0: *aOffset = -1; michael@0: } michael@0: return parent; michael@0: } michael@0: michael@0: // returns the number of things inside aNode. michael@0: // If aNode is text, returns number of characters. If not, returns number of children nodes. michael@0: nsresult michael@0: nsEditor::GetLengthOfDOMNode(nsIDOMNode *aNode, uint32_t &aCount) michael@0: { michael@0: aCount = 0; michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: aCount = node->Length(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::GetPriorNode(nsIDOMNode *aParentNode, michael@0: int32_t aOffset, michael@0: bool aEditableNode, michael@0: nsCOMPtr *aResultNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: *aResultNode = nullptr; michael@0: michael@0: nsCOMPtr parentNode = do_QueryInterface(aParentNode); michael@0: NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetPriorNode(parentNode, aOffset, michael@0: aEditableNode, michael@0: bNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetPriorNode(nsINode* aParentNode, michael@0: int32_t aOffset, michael@0: bool aEditableNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aParentNode); michael@0: michael@0: // If we are at the beginning of the node, or it is a text node, then just michael@0: // look before it. michael@0: if (!aOffset || aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: if (aNoBlockCrossing && IsBlockNode(aParentNode)) { michael@0: // If we aren't allowed to cross blocks, don't look before this block. michael@0: return nullptr; michael@0: } michael@0: return GetPriorNode(aParentNode, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: // else look before the child at 'aOffset' michael@0: if (nsIContent* child = aParentNode->GetChildAt(aOffset)) { michael@0: return GetPriorNode(child, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: // unless there isn't one, in which case we are at the end of the node michael@0: // and want the deep-right child. michael@0: nsIContent* resultNode = GetRightmostChild(aParentNode, aNoBlockCrossing); michael@0: if (!resultNode || !aEditableNode || IsEditable(resultNode)) { michael@0: return resultNode; michael@0: } michael@0: michael@0: // restart the search from the non-editable node we just found michael@0: return GetPriorNode(resultNode, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::GetNextNode(nsIDOMNode *aParentNode, michael@0: int32_t aOffset, michael@0: bool aEditableNode, michael@0: nsCOMPtr *aResultNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: *aResultNode = nullptr; michael@0: michael@0: nsCOMPtr parentNode = do_QueryInterface(aParentNode); michael@0: NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetNextNode(parentNode, aOffset, michael@0: aEditableNode, michael@0: bNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetNextNode(nsINode* aParentNode, michael@0: int32_t aOffset, michael@0: bool aEditableNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aParentNode); michael@0: michael@0: // if aParentNode is a text node, use its location instead michael@0: if (aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: nsINode* parent = aParentNode->GetParentNode(); michael@0: NS_ENSURE_TRUE(parent, nullptr); michael@0: aOffset = parent->IndexOf(aParentNode) + 1; // _after_ the text node michael@0: aParentNode = parent; michael@0: } michael@0: michael@0: // look at the child at 'aOffset' michael@0: nsIContent* child = aParentNode->GetChildAt(aOffset); michael@0: if (child) { michael@0: if (aNoBlockCrossing && IsBlockNode(child)) { michael@0: return child; michael@0: } michael@0: michael@0: nsIContent* resultNode = GetLeftmostChild(child, aNoBlockCrossing); michael@0: if (!resultNode) { michael@0: return child; michael@0: } michael@0: michael@0: if (!IsDescendantOfEditorRoot(resultNode)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!aEditableNode || IsEditable(resultNode)) { michael@0: return resultNode; michael@0: } michael@0: michael@0: // restart the search from the non-editable node we just found michael@0: return GetNextNode(resultNode, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: // unless there isn't one, in which case we are at the end of the node michael@0: // and want the next one. michael@0: if (aNoBlockCrossing && IsBlockNode(aParentNode)) { michael@0: // don't cross out of parent block michael@0: return nullptr; michael@0: } michael@0: michael@0: return GetNextNode(aParentNode, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::GetPriorNode(nsIDOMNode *aCurrentNode, michael@0: bool aEditableNode, michael@0: nsCOMPtr *aResultNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr currentNode = do_QueryInterface(aCurrentNode); michael@0: NS_ENSURE_TRUE(currentNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetPriorNode(currentNode, aEditableNode, michael@0: bNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetPriorNode(nsINode* aCurrentNode, bool aEditableNode, michael@0: bool aNoBlockCrossing /* = false */) michael@0: { michael@0: MOZ_ASSERT(aCurrentNode); michael@0: michael@0: if (!IsDescendantOfEditorRoot(aCurrentNode)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return FindNode(aCurrentNode, false, aEditableNode, aNoBlockCrossing); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::FindNextLeafNode(nsINode *aCurrentNode, michael@0: bool aGoForward, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: // called only by GetPriorNode so we don't need to check params. michael@0: NS_PRECONDITION(IsDescendantOfEditorRoot(aCurrentNode) && michael@0: !IsEditorRoot(aCurrentNode), michael@0: "Bogus arguments"); michael@0: michael@0: nsINode* cur = aCurrentNode; michael@0: for (;;) { michael@0: // if aCurrentNode has a sibling in the right direction, return michael@0: // that sibling's closest child (or itself if it has no children) michael@0: nsIContent* sibling = michael@0: aGoForward ? cur->GetNextSibling() : cur->GetPreviousSibling(); michael@0: if (sibling) { michael@0: if (bNoBlockCrossing && IsBlockNode(sibling)) { michael@0: // don't look inside prevsib, since it is a block michael@0: return sibling; michael@0: } michael@0: nsIContent *leaf = michael@0: aGoForward ? GetLeftmostChild(sibling, bNoBlockCrossing) : michael@0: GetRightmostChild(sibling, bNoBlockCrossing); michael@0: if (!leaf) { michael@0: return sibling; michael@0: } michael@0: michael@0: return leaf; michael@0: } michael@0: michael@0: nsINode *parent = cur->GetParentNode(); michael@0: if (!parent) { michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(IsDescendantOfEditorRoot(parent), michael@0: "We started with a proper descendant of root, and should stop " michael@0: "if we ever hit the root, so we better have a descendant of " michael@0: "root now!"); michael@0: if (IsEditorRoot(parent) || michael@0: (bNoBlockCrossing && IsBlockNode(parent))) { michael@0: return nullptr; michael@0: } michael@0: michael@0: cur = parent; michael@0: } michael@0: michael@0: NS_NOTREACHED("What part of for(;;) do you not understand?"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::GetNextNode(nsIDOMNode* aCurrentNode, michael@0: bool aEditableNode, michael@0: nsCOMPtr *aResultNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: nsCOMPtr currentNode = do_QueryInterface(aCurrentNode); michael@0: if (!currentNode || !aResultNode) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: *aResultNode = do_QueryInterface(GetNextNode(currentNode, aEditableNode, michael@0: bNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetNextNode(nsINode* aCurrentNode, michael@0: bool aEditableNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aCurrentNode); michael@0: michael@0: if (!IsDescendantOfEditorRoot(aCurrentNode)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return FindNode(aCurrentNode, true, aEditableNode, bNoBlockCrossing); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::FindNode(nsINode *aCurrentNode, michael@0: bool aGoForward, michael@0: bool aEditableNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: if (IsEditorRoot(aCurrentNode)) { michael@0: // Don't allow traversal above the root node! This helps michael@0: // prevent us from accidentally editing browser content michael@0: // when the editor is in a text widget. michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr candidate = michael@0: FindNextLeafNode(aCurrentNode, aGoForward, bNoBlockCrossing); michael@0: michael@0: if (!candidate) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!aEditableNode || IsEditable(candidate)) { michael@0: return candidate; michael@0: } michael@0: michael@0: return FindNode(candidate, aGoForward, aEditableNode, bNoBlockCrossing); michael@0: } michael@0: michael@0: nsIDOMNode* michael@0: nsEditor::GetRightmostChild(nsIDOMNode* aCurrentNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: nsCOMPtr currentNode = do_QueryInterface(aCurrentNode); michael@0: nsIContent* result = GetRightmostChild(currentNode, bNoBlockCrossing); michael@0: return result ? result->AsDOMNode() : nullptr; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetRightmostChild(nsINode *aCurrentNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aCurrentNode, nullptr); michael@0: nsIContent *cur = aCurrentNode->GetLastChild(); michael@0: if (!cur) { michael@0: return nullptr; michael@0: } michael@0: for (;;) { michael@0: if (bNoBlockCrossing && IsBlockNode(cur)) { michael@0: return cur; michael@0: } michael@0: nsIContent* next = cur->GetLastChild(); michael@0: if (!next) { michael@0: return cur; michael@0: } michael@0: cur = next; michael@0: } michael@0: michael@0: NS_NOTREACHED("What part of for(;;) do you not understand?"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsEditor::GetLeftmostChild(nsINode *aCurrentNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aCurrentNode, nullptr); michael@0: nsIContent *cur = aCurrentNode->GetFirstChild(); michael@0: if (!cur) { michael@0: return nullptr; michael@0: } michael@0: for (;;) { michael@0: if (bNoBlockCrossing && IsBlockNode(cur)) { michael@0: return cur; michael@0: } michael@0: nsIContent *next = cur->GetFirstChild(); michael@0: if (!next) { michael@0: return cur; michael@0: } michael@0: cur = next; michael@0: } michael@0: michael@0: NS_NOTREACHED("What part of for(;;) do you not understand?"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIDOMNode* michael@0: nsEditor::GetLeftmostChild(nsIDOMNode* aCurrentNode, michael@0: bool bNoBlockCrossing) michael@0: { michael@0: nsCOMPtr currentNode = do_QueryInterface(aCurrentNode); michael@0: nsIContent* result = GetLeftmostChild(currentNode, bNoBlockCrossing); michael@0: return result ? result->AsDOMNode() : nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsBlockNode(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return IsBlockNode(node); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsBlockNode(nsINode* aNode) michael@0: { michael@0: // stub to be overridden in nsHTMLEditor. michael@0: // screwing around with the class hierarchy here in order michael@0: // to not duplicate the code in GetNextNode/GetPrevNode michael@0: // across both nsEditor/nsHTMLEditor. michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::CanContain(nsIDOMNode* aParent, nsIDOMNode* aChild) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: NS_ENSURE_TRUE(parent, false); michael@0: michael@0: switch (parent->NodeType()) { michael@0: case nsIDOMNode::ELEMENT_NODE: michael@0: case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: michael@0: return TagCanContain(parent->Tag(), aChild); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::CanContainTag(nsIDOMNode* aParent, nsIAtom* aChildTag) michael@0: { michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: NS_ENSURE_TRUE(parent, false); michael@0: michael@0: switch (parent->NodeType()) { michael@0: case nsIDOMNode::ELEMENT_NODE: michael@0: case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: michael@0: return TagCanContainTag(parent->Tag(), aChildTag); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::TagCanContain(nsIAtom* aParentTag, nsIDOMNode* aChild) michael@0: { michael@0: nsCOMPtr child = do_QueryInterface(aChild); michael@0: NS_ENSURE_TRUE(child, false); michael@0: michael@0: switch (child->NodeType()) { michael@0: case nsIDOMNode::TEXT_NODE: michael@0: case nsIDOMNode::ELEMENT_NODE: michael@0: case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: michael@0: return TagCanContainTag(aParentTag, child->Tag()); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::TagCanContainTag(nsIAtom* aParentTag, nsIAtom* aChildTag) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsRoot(nsIDOMNode* inNode) michael@0: { michael@0: NS_ENSURE_TRUE(inNode, false); michael@0: michael@0: nsCOMPtr rootNode = do_QueryInterface(GetRoot()); michael@0: michael@0: return inNode == rootNode; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsRoot(nsINode* inNode) michael@0: { michael@0: NS_ENSURE_TRUE(inNode, false); michael@0: michael@0: nsCOMPtr rootNode = GetRoot(); michael@0: michael@0: return inNode == rootNode; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsEditorRoot(nsINode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: nsCOMPtr rootNode = GetEditorRoot(); michael@0: return aNode == rootNode; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsDescendantOfRoot(nsIDOMNode* inNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: return IsDescendantOfRoot(node); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsDescendantOfRoot(nsINode* inNode) michael@0: { michael@0: NS_ENSURE_TRUE(inNode, false); michael@0: nsCOMPtr root = GetRoot(); michael@0: NS_ENSURE_TRUE(root, false); michael@0: michael@0: return nsContentUtils::ContentIsDescendantOf(inNode, root); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsDescendantOfEditorRoot(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return IsDescendantOfEditorRoot(node); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsDescendantOfEditorRoot(nsINode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: nsCOMPtr root = GetEditorRoot(); michael@0: NS_ENSURE_TRUE(root, false); michael@0: michael@0: return nsContentUtils::ContentIsDescendantOf(aNode, root); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsContainer(nsIDOMNode *aNode) michael@0: { michael@0: return aNode ? true : false; michael@0: } michael@0: michael@0: static inline bool michael@0: IsElementVisible(dom::Element* aElement) michael@0: { michael@0: if (aElement->GetPrimaryFrame()) { michael@0: // It's visible, for our purposes michael@0: return true; michael@0: } michael@0: michael@0: nsIContent *cur = aElement; michael@0: for (; ;) { michael@0: // Walk up the tree looking for the nearest ancestor with a frame. michael@0: // The state of the child right below it will determine whether michael@0: // we might possibly have a frame or not. michael@0: bool haveLazyBitOnChild = cur->HasFlag(NODE_NEEDS_FRAME); michael@0: cur = cur->GetFlattenedTreeParent(); michael@0: if (!cur) { michael@0: if (!haveLazyBitOnChild) { michael@0: // None of our ancestors have lazy bits set, so we shouldn't michael@0: // have a frame michael@0: return false; michael@0: } michael@0: michael@0: // The root has a lazy frame construction bit. We need to check michael@0: // our style. michael@0: break; michael@0: } michael@0: michael@0: if (cur->GetPrimaryFrame()) { michael@0: if (!haveLazyBitOnChild) { michael@0: // Our ancestor directly under |cur| doesn't have lazy bits; michael@0: // that means we won't get a frame michael@0: return false; michael@0: } michael@0: michael@0: if (cur->GetPrimaryFrame()->IsLeaf()) { michael@0: // Nothing under here will ever get frames michael@0: return false; michael@0: } michael@0: michael@0: // Otherwise, we might end up with a frame when that lazy bit is michael@0: // processed. Figure out our actual style. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Now it might be that we have no frame because we're in a michael@0: // display:none subtree, or it might be that we're just dealing with michael@0: // lazy frame construction and it hasn't happened yet. Check which michael@0: // one it is. michael@0: nsRefPtr styleContext = michael@0: nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, michael@0: nullptr, nullptr); michael@0: if (styleContext) { michael@0: return styleContext->StyleDisplay()->mDisplay != NS_STYLE_DISPLAY_NONE; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsEditable(nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: return IsEditable(content); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsEditable(nsIContent *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: michael@0: if (IsMozEditorBogusNode(aNode) || !IsModifiableNode(aNode)) return false; michael@0: michael@0: // see if it has a frame. If so, we'll edit it. michael@0: // special case for textnodes: frame must have width. michael@0: if (aNode->IsElement() && !IsElementVisible(aNode->AsElement())) { michael@0: // If the element has no frame, it's not editable. Note that we michael@0: // need to check IsElement() here, because some of our tests michael@0: // rely on frameless textnodes being visible. michael@0: return false; michael@0: } michael@0: switch (aNode->NodeType()) { michael@0: case nsIDOMNode::ELEMENT_NODE: michael@0: case nsIDOMNode::TEXT_NODE: michael@0: return true; // element or text node; not invisible michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsMozEditorBogusNode(nsIContent *element) michael@0: { michael@0: return element && michael@0: element->AttrValueIs(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, michael@0: kMOZEditorBogusNodeValue, eCaseMatters); michael@0: } michael@0: michael@0: uint32_t michael@0: nsEditor::CountEditableChildren(nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: uint32_t count = 0; michael@0: for (nsIContent* child = aNode->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (IsEditable(child)) { michael@0: ++count; michael@0: } michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: //END nsEditor static utility methods michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::IncrementModificationCount(int32_t inNumMods) michael@0: { michael@0: uint32_t oldModCount = mModCount; michael@0: michael@0: mModCount += inNumMods; michael@0: michael@0: if ((oldModCount == 0 && mModCount != 0) michael@0: || (oldModCount != 0 && mModCount == 0)) michael@0: NotifyDocumentListeners(eDocumentStateChanged); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::GetModificationCount(int32_t *outModCount) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(outModCount); michael@0: *outModCount = mModCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::ResetModificationCount() michael@0: { michael@0: bool doNotify = (mModCount != 0); michael@0: michael@0: mModCount = 0; michael@0: michael@0: if (doNotify) michael@0: NotifyDocumentListeners(eDocumentStateChanged); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //END nsEditor Private methods michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetTag: digs out the atom for the tag of this node michael@0: // michael@0: nsIAtom * michael@0: nsEditor::GetTag(nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: michael@0: if (!content) michael@0: { michael@0: NS_ASSERTION(aNode, "null node passed to nsEditor::Tag()"); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: return content->Tag(); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetTagString: digs out string for the tag of this node michael@0: // michael@0: nsresult michael@0: nsEditor::GetTagString(nsIDOMNode *aNode, nsAString& outString) michael@0: { michael@0: if (!aNode) michael@0: { michael@0: NS_NOTREACHED("null node passed to nsEditor::GetTag()"); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: nsIAtom *atom = GetTag(aNode); michael@0: if (!atom) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: atom->ToString(outString); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // NodesSameType: do these nodes have the same tag? michael@0: // michael@0: bool michael@0: nsEditor::NodesSameType(nsIDOMNode *aNode1, nsIDOMNode *aNode2) michael@0: { michael@0: if (!aNode1 || !aNode2) { michael@0: NS_NOTREACHED("null node passed to nsEditor::NodesSameType()"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr content1 = do_QueryInterface(aNode1); michael@0: NS_ENSURE_TRUE(content1, false); michael@0: michael@0: nsCOMPtr content2 = do_QueryInterface(aNode2); michael@0: NS_ENSURE_TRUE(content2, false); michael@0: michael@0: return AreNodesSameType(content1, content2); michael@0: } michael@0: michael@0: /* virtual */ michael@0: bool michael@0: nsEditor::AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2) michael@0: { michael@0: MOZ_ASSERT(aNode1); michael@0: MOZ_ASSERT(aNode2); michael@0: return aNode1->Tag() == aNode2->Tag(); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsTextNode: true if node of dom type text michael@0: // michael@0: bool michael@0: nsEditor::IsTextNode(nsIDOMNode *aNode) michael@0: { michael@0: if (!aNode) michael@0: { michael@0: NS_NOTREACHED("null node passed to IsTextNode()"); michael@0: return false; michael@0: } michael@0: michael@0: uint16_t nodeType; michael@0: aNode->GetNodeType(&nodeType); michael@0: return (nodeType == nsIDOMNode::TEXT_NODE); michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsTextNode(nsINode *aNode) michael@0: { michael@0: return aNode->NodeType() == nsIDOMNode::TEXT_NODE; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetChildAt: returns the node at this position index in the parent michael@0: // michael@0: nsCOMPtr michael@0: nsEditor::GetChildAt(nsIDOMNode *aParent, int32_t aOffset) michael@0: { michael@0: nsCOMPtr resultNode; michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(aParent); michael@0: michael@0: NS_ENSURE_TRUE(parent, resultNode); michael@0: michael@0: resultNode = do_QueryInterface(parent->GetChildAt(aOffset)); michael@0: michael@0: return resultNode; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNodeAtRangeOffsetPoint: returns the node at this position in a range, michael@0: // assuming that aParentOrNode is the node itself if it's a text node, or michael@0: // the node's parent otherwise. michael@0: // michael@0: nsCOMPtr michael@0: nsEditor::GetNodeAtRangeOffsetPoint(nsIDOMNode* aParentOrNode, int32_t aOffset) michael@0: { michael@0: if (IsTextNode(aParentOrNode)) { michael@0: return aParentOrNode; michael@0: } michael@0: return GetChildAt(aParentOrNode, aOffset); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetStartNodeAndOffset: returns whatever the start parent & offset is of michael@0: // the first range in the selection. michael@0: nsresult michael@0: nsEditor::GetStartNodeAndOffset(nsISelection *aSelection, michael@0: nsIDOMNode **outStartNode, michael@0: int32_t *outStartOffset) michael@0: { michael@0: NS_ENSURE_TRUE(outStartNode && outStartOffset && aSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr startNode; michael@0: nsresult rv = GetStartNodeAndOffset(static_cast(aSelection), michael@0: getter_AddRefs(startNode), michael@0: outStartOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (startNode) { michael@0: NS_ADDREF(*outStartNode = startNode->AsDOMNode()); michael@0: } else { michael@0: *outStartNode = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::GetStartNodeAndOffset(Selection* aSelection, nsINode** aStartNode, michael@0: int32_t* aStartOffset) michael@0: { michael@0: MOZ_ASSERT(aSelection); michael@0: MOZ_ASSERT(aStartNode); michael@0: MOZ_ASSERT(aStartOffset); michael@0: michael@0: *aStartNode = nullptr; michael@0: *aStartOffset = 0; michael@0: michael@0: NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE); michael@0: michael@0: const nsRange* range = aSelection->GetRangeAt(0); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); michael@0: michael@0: NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE); michael@0: michael@0: NS_IF_ADDREF(*aStartNode = range->GetStartParent()); michael@0: *aStartOffset = range->StartOffset(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetEndNodeAndOffset: returns whatever the end parent & offset is of michael@0: // the first range in the selection. michael@0: nsresult michael@0: nsEditor::GetEndNodeAndOffset(nsISelection *aSelection, michael@0: nsIDOMNode **outEndNode, michael@0: int32_t *outEndOffset) michael@0: { michael@0: NS_ENSURE_TRUE(outEndNode && outEndOffset && aSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr endNode; michael@0: nsresult rv = GetEndNodeAndOffset(static_cast(aSelection), michael@0: getter_AddRefs(endNode), michael@0: outEndOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (endNode) { michael@0: NS_ADDREF(*outEndNode = endNode->AsDOMNode()); michael@0: } else { michael@0: *outEndNode = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::GetEndNodeAndOffset(Selection* aSelection, nsINode** aEndNode, michael@0: int32_t* aEndOffset) michael@0: { michael@0: MOZ_ASSERT(aSelection); michael@0: MOZ_ASSERT(aEndNode); michael@0: MOZ_ASSERT(aEndOffset); michael@0: michael@0: *aEndNode = nullptr; michael@0: *aEndOffset = 0; michael@0: michael@0: NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE); michael@0: michael@0: const nsRange* range = aSelection->GetRangeAt(0); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); michael@0: michael@0: NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE); michael@0: michael@0: NS_IF_ADDREF(*aEndNode = range->GetEndParent()); michael@0: *aEndOffset = range->EndOffset(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsPreformatted: checks the style info for the node for the preformatted michael@0: // text style. michael@0: nsresult michael@0: nsEditor::IsPreformatted(nsIDOMNode *aNode, bool *aResult) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: michael@0: NS_ENSURE_TRUE(aResult && content, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Look at the node (and its parent if it's not an element), and grab its style context michael@0: nsRefPtr elementStyle; michael@0: if (!content->IsElement()) { michael@0: content = content->GetParent(); michael@0: } michael@0: if (content && content->IsElement()) { michael@0: elementStyle = nsComputedDOMStyle::GetStyleContextForElementNoFlush(content->AsElement(), michael@0: nullptr, michael@0: ps); michael@0: } michael@0: michael@0: if (!elementStyle) michael@0: { michael@0: // Consider nodes without a style context to be NOT preformatted: michael@0: // For instance, this is true of JS tags inside the body (which show michael@0: // up as #text nodes but have no style context). michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: const nsStyleText* styleText = elementStyle->StyleText(); michael@0: michael@0: *aResult = styleText->WhiteSpaceIsSignificant(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // SplitNodeDeep: this splits a node "deeply", splitting children as michael@0: // appropriate. The place to split is represented by michael@0: // a dom point at {splitPointParent, splitPointOffset}. michael@0: // That dom point must be inside aNode, which is the node to michael@0: // split. outOffset is set to the offset in the parent of aNode where michael@0: // the split terminates - where you would want to insert michael@0: // a new element, for instance, if that's why you were splitting michael@0: // the node. michael@0: // michael@0: nsresult michael@0: nsEditor::SplitNodeDeep(nsIDOMNode *aNode, michael@0: nsIDOMNode *aSplitPointParent, michael@0: int32_t aSplitPointOffset, michael@0: int32_t *outOffset, michael@0: bool aNoEmptyContainers, michael@0: nsCOMPtr *outLeftNode, michael@0: nsCOMPtr *outRightNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node && aSplitPointParent && outOffset, NS_ERROR_NULL_POINTER); michael@0: int32_t offset = aSplitPointOffset; michael@0: michael@0: if (outLeftNode) *outLeftNode = nullptr; michael@0: if (outRightNode) *outRightNode = nullptr; michael@0: michael@0: nsCOMPtr nodeToSplit = do_QueryInterface(aSplitPointParent); michael@0: while (nodeToSplit) { michael@0: // need to insert rules code call here to do things like michael@0: // not split a list if you are after the last
  • or before the first, etc. michael@0: // for now we just have some smarts about unneccessarily splitting michael@0: // textnodes, which should be universal enough to put straight in michael@0: // this nsEditor routine. michael@0: michael@0: nsCOMPtr nodeAsText = do_QueryInterface(nodeToSplit); michael@0: uint32_t len = nodeToSplit->Length(); michael@0: bool bDoSplit = false; michael@0: michael@0: if (!(aNoEmptyContainers || nodeAsText) || (offset && (offset != (int32_t)len))) michael@0: { michael@0: bDoSplit = true; michael@0: nsCOMPtr tempNode; michael@0: nsresult rv = SplitNode(nodeToSplit->AsDOMNode(), offset, michael@0: getter_AddRefs(tempNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (outRightNode) { michael@0: *outRightNode = nodeToSplit->AsDOMNode(); michael@0: } michael@0: if (outLeftNode) { michael@0: *outLeftNode = tempNode; michael@0: } michael@0: } michael@0: michael@0: nsINode* parentNode = nodeToSplit->GetParentNode(); michael@0: NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE); michael@0: michael@0: if (!bDoSplit && offset) { michael@0: // must be "end of text node" case, we didn't split it, just move past it michael@0: offset = parentNode->IndexOf(nodeToSplit) + 1; michael@0: if (outLeftNode) { michael@0: *outLeftNode = nodeToSplit->AsDOMNode(); michael@0: } michael@0: } else { michael@0: offset = parentNode->IndexOf(nodeToSplit); michael@0: if (outRightNode) { michael@0: *outRightNode = nodeToSplit->AsDOMNode(); michael@0: } michael@0: } michael@0: michael@0: if (nodeToSplit == node) { michael@0: // we split all the way up to (and including) aNode; we're done michael@0: break; michael@0: } michael@0: michael@0: nodeToSplit = parentNode; michael@0: } michael@0: michael@0: if (!nodeToSplit) { michael@0: NS_NOTREACHED("null node obtained in nsEditor::SplitNodeDeep()"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *outOffset = offset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // JoinNodeDeep: this joins two like nodes "deeply", joining children as michael@0: // appropriate. michael@0: nsresult michael@0: nsEditor::JoinNodeDeep(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: nsCOMPtr *aOutJoinNode, michael@0: int32_t *outOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aLeftNode && aRightNode && aOutJoinNode && outOffset, NS_ERROR_NULL_POINTER); michael@0: michael@0: // while the rightmost children and their descendants of the left node michael@0: // match the leftmost children and their descendants of the right node michael@0: // join them up. Can you say that three times fast? michael@0: michael@0: nsCOMPtr leftNodeToJoin = do_QueryInterface(aLeftNode); michael@0: nsCOMPtr rightNodeToJoin = do_QueryInterface(aRightNode); michael@0: nsCOMPtr parentNode,tmp; michael@0: nsresult res = NS_OK; michael@0: michael@0: rightNodeToJoin->GetParentNode(getter_AddRefs(parentNode)); michael@0: michael@0: while (leftNodeToJoin && rightNodeToJoin && parentNode && michael@0: NodesSameType(leftNodeToJoin, rightNodeToJoin)) michael@0: { michael@0: // adjust out params michael@0: uint32_t length; michael@0: res = GetLengthOfDOMNode(leftNodeToJoin, length); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aOutJoinNode = rightNodeToJoin; michael@0: *outOffset = length; michael@0: michael@0: // do the join michael@0: res = JoinNodes(leftNodeToJoin, rightNodeToJoin, parentNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (IsTextNode(parentNode)) // we've joined all the way down to text nodes, we're done! michael@0: return NS_OK; michael@0: michael@0: else michael@0: { michael@0: // get new left and right nodes, and begin anew michael@0: parentNode = rightNodeToJoin; michael@0: leftNodeToJoin = GetChildAt(parentNode, length-1); michael@0: rightNodeToJoin = GetChildAt(parentNode, length); michael@0: michael@0: // skip over non-editable nodes michael@0: while (leftNodeToJoin && !IsEditable(leftNodeToJoin)) michael@0: { michael@0: leftNodeToJoin->GetPreviousSibling(getter_AddRefs(tmp)); michael@0: leftNodeToJoin = tmp; michael@0: } michael@0: if (!leftNodeToJoin) break; michael@0: michael@0: while (rightNodeToJoin && !IsEditable(rightNodeToJoin)) michael@0: { michael@0: rightNodeToJoin->GetNextSibling(getter_AddRefs(tmp)); michael@0: rightNodeToJoin = tmp; michael@0: } michael@0: if (!rightNodeToJoin) break; michael@0: } michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: void michael@0: nsEditor::BeginUpdateViewBatch() michael@0: { michael@0: NS_PRECONDITION(mUpdateCount >= 0, "bad state"); michael@0: michael@0: if (0 == mUpdateCount) michael@0: { michael@0: // Turn off selection updates and notifications. michael@0: michael@0: nsCOMPtr selection; michael@0: GetSelection(getter_AddRefs(selection)); michael@0: michael@0: if (selection) michael@0: { michael@0: nsCOMPtr selPrivate(do_QueryInterface(selection)); michael@0: selPrivate->StartBatchChanges(); michael@0: } michael@0: } michael@0: michael@0: mUpdateCount++; michael@0: } michael@0: michael@0: michael@0: nsresult nsEditor::EndUpdateViewBatch() michael@0: { michael@0: NS_PRECONDITION(mUpdateCount > 0, "bad state"); michael@0: michael@0: if (mUpdateCount <= 0) michael@0: { michael@0: mUpdateCount = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mUpdateCount--; michael@0: michael@0: if (0 == mUpdateCount) michael@0: { michael@0: // Turn selection updating and notifications back on. michael@0: michael@0: nsCOMPtrselection; michael@0: GetSelection(getter_AddRefs(selection)); michael@0: michael@0: if (selection) { michael@0: nsCOMPtrselPrivate(do_QueryInterface(selection)); michael@0: selPrivate->EndBatchChanges(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::GetShouldTxnSetSelection() michael@0: { michael@0: return mShouldTxnSetSelection; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::DeleteSelectionImpl(EDirection aAction, michael@0: EStripWrappers aStripWrappers) michael@0: { michael@0: MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); michael@0: michael@0: nsCOMPtrselection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsRefPtr txn; michael@0: nsCOMPtr deleteNode; michael@0: int32_t deleteCharOffset = 0, deleteCharLength = 0; michael@0: res = CreateTxnForDeleteSelection(aAction, getter_AddRefs(txn), michael@0: getter_AddRefs(deleteNode), michael@0: &deleteCharOffset, &deleteCharLength); michael@0: nsCOMPtr deleteCharData(do_QueryInterface(deleteNode)); michael@0: michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: nsAutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); michael@0: int32_t i; michael@0: // Notify nsIEditActionListener::WillDelete[Selection|Text|Node] michael@0: if (!deleteNode) michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillDeleteSelection(selection); michael@0: else if (deleteCharData) michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillDeleteText(deleteCharData, deleteCharOffset, 1); michael@0: else michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->WillDeleteNode(deleteNode->AsDOMNode()); michael@0: michael@0: // Delete the specified amount michael@0: res = DoTransaction(txn); michael@0: michael@0: // Notify nsIEditActionListener::DidDelete[Selection|Text|Node] michael@0: if (!deleteNode) michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidDeleteSelection(selection); michael@0: else if (deleteCharData) michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidDeleteText(deleteCharData, deleteCharOffset, 1, res); michael@0: else michael@0: for (i = 0; i < mActionListeners.Count(); i++) michael@0: mActionListeners[i]->DidDeleteNode(deleteNode->AsDOMNode(), res); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: // XXX: error handling in this routine needs to be cleaned up! michael@0: NS_IMETHODIMP michael@0: nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag, michael@0: nsIDOMNode ** aNewNode) michael@0: { michael@0: nsresult result = DeleteSelectionAndPrepareToCreateNode(); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = selection->GetAnchorNode(); michael@0: uint32_t offset = selection->AnchorOffset(); michael@0: michael@0: nsCOMPtr newNode; michael@0: result = CreateNode(aTag, node->AsDOMNode(), offset, michael@0: getter_AddRefs(newNode)); michael@0: // XXX: ERROR_HANDLING check result, and make sure aNewNode is set correctly michael@0: // in success/failure cases michael@0: *aNewNode = newNode; michael@0: NS_IF_ADDREF(*aNewNode); michael@0: michael@0: // we want the selection to be just after the new node michael@0: return selection->Collapse(node, offset + 1); michael@0: } michael@0: michael@0: michael@0: /* Non-interface, protected methods */ michael@0: michael@0: TextComposition* michael@0: nsEditor::GetComposition() const michael@0: { michael@0: return mComposition; michael@0: } michael@0: michael@0: bool michael@0: nsEditor::IsIMEComposing() const michael@0: { michael@0: return mComposition && mComposition->IsComposing(); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::DeleteSelectionAndPrepareToCreateNode() michael@0: { michael@0: nsresult res; michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: MOZ_ASSERT(selection->GetAnchorFocusRange()); michael@0: michael@0: if (!selection->GetAnchorFocusRange()->Collapsed()) { michael@0: res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: MOZ_ASSERT(selection->GetAnchorFocusRange() && michael@0: selection->GetAnchorFocusRange()->Collapsed(), michael@0: "Selection not collapsed after delete"); michael@0: } michael@0: michael@0: // If the selection is a chardata node, split it if necessary and compute michael@0: // where to put the new node michael@0: nsCOMPtr node = selection->GetAnchorNode(); michael@0: MOZ_ASSERT(node, "Selection has no ranges in it"); michael@0: michael@0: if (node && node->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: NS_ASSERTION(node->GetParentNode(), michael@0: "It's impossible to insert into chardata with no parent -- " michael@0: "fix the caller"); michael@0: NS_ENSURE_STATE(node->GetParentNode()); michael@0: michael@0: uint32_t offset = selection->AnchorOffset(); michael@0: michael@0: if (offset == 0) { michael@0: res = selection->Collapse(node->GetParentNode(), michael@0: node->GetParentNode()->IndexOf(node)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(res)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (offset == node->Length()) { michael@0: res = selection->Collapse(node->GetParentNode(), michael@0: node->GetParentNode()->IndexOf(node) + 1); michael@0: MOZ_ASSERT(NS_SUCCEEDED(res)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: nsCOMPtr tmp; michael@0: res = SplitNode(node->AsDOMNode(), offset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = selection->Collapse(node->GetParentNode(), michael@0: node->GetParentNode()->IndexOf(node)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(res)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsEditor::DoAfterDoTransaction(nsITransaction *aTxn) michael@0: { michael@0: bool isTransientTransaction; michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: aTxn->GetIsTransient(&isTransientTransaction))); michael@0: michael@0: if (!isTransientTransaction) michael@0: { michael@0: // we need to deal here with the case where the user saved after some michael@0: // edits, then undid one or more times. Then, the undo count is -ve, michael@0: // but we can't let a do take it back to zero. So we flip it up to michael@0: // a +ve number. michael@0: int32_t modCount; michael@0: GetModificationCount(&modCount); michael@0: if (modCount < 0) michael@0: modCount = -modCount; michael@0: michael@0: // don't count transient transactions michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: IncrementModificationCount(1))); michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsEditor::DoAfterUndoTransaction() michael@0: { michael@0: // all undoable transactions are non-transient michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: IncrementModificationCount(-1))); michael@0: } michael@0: michael@0: void michael@0: nsEditor::DoAfterRedoTransaction() michael@0: { michael@0: // all redoable transactions are non-transient michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: IncrementModificationCount(1))); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CreateTxnForSetAttribute(nsIDOMElement *aElement, michael@0: const nsAString& aAttribute, michael@0: const nsAString& aValue, michael@0: ChangeAttributeTxn ** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new ChangeAttributeTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aElement, aAttribute, aValue, false); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CreateTxnForRemoveAttribute(nsIDOMElement *aElement, michael@0: const nsAString& aAttribute, michael@0: ChangeAttributeTxn ** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new ChangeAttributeTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aElement, aAttribute, EmptyString(), true); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateTxnForCreateElement(const nsAString& aTag, michael@0: nsIDOMNode *aParent, michael@0: int32_t aPosition, michael@0: CreateElementTxn ** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new CreateElementTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aTag, aParent, aPosition); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsEditor::CreateTxnForInsertElement(nsIDOMNode * aNode, michael@0: nsIDOMNode * aParent, michael@0: int32_t aPosition, michael@0: InsertElementTxn ** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new InsertElementTxn(); michael@0: michael@0: nsresult rv = txn->Init(aNode, aParent, aPosition, this); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::CreateTxnForDeleteNode(nsINode* aNode, DeleteNodeTxn** aTxn) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new DeleteNodeTxn(); michael@0: michael@0: nsresult res = txn->Init(this, aNode, &mRangeUpdater); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: txn.forget(aTxn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CreateTxnForIMEText(const nsAString& aStringToInsert, michael@0: IMETextTxn ** aTxn) michael@0: { michael@0: NS_ASSERTION(aTxn, "illegal value- null ptr- aTxn"); michael@0: michael@0: nsRefPtr txn = new IMETextTxn(); michael@0: michael@0: // During handling IME composition, mComposition must have been initialized. michael@0: // TODO: We can simplify IMETextTxn::Init() with TextComposition class. michael@0: nsresult rv = txn->Init(mIMETextNode, mIMETextOffset, michael@0: mComposition->String().Length(), michael@0: mComposition->GetRanges(), aStringToInsert, this); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CreateTxnForAddStyleSheet(nsCSSStyleSheet* aSheet, AddStyleSheetTxn* *aTxn) michael@0: { michael@0: nsRefPtr txn = new AddStyleSheetTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aSheet); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsEditor::CreateTxnForRemoveStyleSheet(nsCSSStyleSheet* aSheet, RemoveStyleSheetTxn* *aTxn) michael@0: { michael@0: nsRefPtr txn = new RemoveStyleSheetTxn(); michael@0: michael@0: nsresult rv = txn->Init(this, aSheet); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: txn.forget(aTxn); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsEditor::CreateTxnForDeleteSelection(EDirection aAction, michael@0: EditAggregateTxn** aTxn, michael@0: nsINode** aNode, michael@0: int32_t* aOffset, michael@0: int32_t* aLength) michael@0: { michael@0: MOZ_ASSERT(aTxn); michael@0: *aTxn = nullptr; michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_STATE(selection); michael@0: michael@0: // Check whether the selection is collapsed and we should do nothing: michael@0: if (selection->Collapsed() && aAction == eNone) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // allocate the out-param transaction michael@0: nsRefPtr aggTxn = new EditAggregateTxn(); michael@0: michael@0: for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) { michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: NS_ENSURE_STATE(range); michael@0: michael@0: // Same with range as with selection; if it is collapsed and action michael@0: // is eNone, do nothing. michael@0: if (!range->Collapsed()) { michael@0: nsRefPtr txn = new DeleteRangeTxn(); michael@0: txn->Init(this, range, &mRangeUpdater); michael@0: aggTxn->AppendChild(txn); michael@0: } else if (aAction != eNone) { michael@0: // we have an insertion point. delete the thing in front of it or michael@0: // behind it, depending on aAction michael@0: nsresult res = CreateTxnForDeleteInsertionPoint(range, aAction, aggTxn, michael@0: aNode, aOffset, aLength); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: aggTxn.forget(aTxn); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData, michael@0: uint32_t aOffset, michael@0: EDirection aDirection, michael@0: DeleteTextTxn** aTxn) michael@0: { michael@0: NS_ASSERTION(aDirection == eNext || aDirection == ePrevious, michael@0: "invalid direction"); michael@0: nsAutoString data; michael@0: aData->GetData(data); michael@0: NS_ASSERTION(data.Length(), "Trying to delete from a zero-length node"); michael@0: NS_ENSURE_STATE(data.Length()); michael@0: michael@0: uint32_t segOffset = aOffset, segLength = 1; michael@0: if (aDirection == eNext) { michael@0: if (segOffset + 1 < data.Length() && michael@0: NS_IS_HIGH_SURROGATE(data[segOffset]) && michael@0: NS_IS_LOW_SURROGATE(data[segOffset+1])) { michael@0: // delete both halves of the surrogate pair michael@0: ++segLength; michael@0: } michael@0: } else if (aOffset > 0) { michael@0: --segOffset; michael@0: if (segOffset > 0 && michael@0: NS_IS_LOW_SURROGATE(data[segOffset]) && michael@0: NS_IS_HIGH_SURROGATE(data[segOffset-1])) { michael@0: ++segLength; michael@0: --segOffset; michael@0: } michael@0: } else { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return CreateTxnForDeleteText(aData, segOffset, segLength, aTxn); michael@0: } michael@0: michael@0: //XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior michael@0: //are not implemented michael@0: nsresult michael@0: nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange, michael@0: EDirection aAction, michael@0: EditAggregateTxn* aTxn, michael@0: nsINode** aNode, michael@0: int32_t* aOffset, michael@0: int32_t* aLength) michael@0: { michael@0: MOZ_ASSERT(aAction != eNone); michael@0: michael@0: nsresult res; michael@0: michael@0: // get the node and offset of the insertion point michael@0: nsCOMPtr node = aRange->GetStartParent(); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: int32_t offset = aRange->StartOffset(); michael@0: michael@0: // determine if the insertion point is at the beginning, middle, or end of michael@0: // the node michael@0: nsCOMPtr nodeAsCharData = do_QueryInterface(node); michael@0: michael@0: uint32_t count = node->Length(); michael@0: michael@0: bool isFirst = (0 == offset); michael@0: bool isLast = (count == (uint32_t)offset); michael@0: michael@0: // XXX: if isFirst && isLast, then we'll need to delete the node michael@0: // as well as the 1 child michael@0: michael@0: // build a transaction for deleting the appropriate data michael@0: // XXX: this has to come from rule section michael@0: if (aAction == ePrevious && isFirst) { michael@0: // we're backspacing from the beginning of the node. Delete the first michael@0: // thing to our left michael@0: nsCOMPtr priorNode = GetPriorNode(node, true); michael@0: NS_ENSURE_STATE(priorNode); michael@0: michael@0: // there is a priorNode, so delete its last child (if chardata, delete the michael@0: // last char). if it has no children, delete it michael@0: nsCOMPtr priorNodeAsCharData = michael@0: do_QueryInterface(priorNode); michael@0: if (priorNodeAsCharData) { michael@0: uint32_t length = priorNode->Length(); michael@0: // Bail out for empty chardata XXX: Do we want to do something else? michael@0: NS_ENSURE_STATE(length); michael@0: nsRefPtr txn; michael@0: res = CreateTxnForDeleteCharacter(priorNodeAsCharData, length, michael@0: ePrevious, getter_AddRefs(txn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aOffset = txn->GetOffset(); michael@0: *aLength = txn->GetNumCharsToDelete(); michael@0: aTxn->AppendChild(txn); michael@0: } else { michael@0: // priorNode is not chardata, so tell its parent to delete it michael@0: nsRefPtr txn; michael@0: res = CreateTxnForDeleteNode(priorNode, getter_AddRefs(txn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: aTxn->AppendChild(txn); michael@0: } michael@0: michael@0: NS_ADDREF(*aNode = priorNode); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aAction == eNext && isLast) { michael@0: // we're deleting from the end of the node. Delete the first thing to our michael@0: // right michael@0: nsCOMPtr nextNode = GetNextNode(node, true); michael@0: NS_ENSURE_STATE(nextNode); michael@0: michael@0: // there is a nextNode, so delete its first child (if chardata, delete the michael@0: // first char). if it has no children, delete it michael@0: nsCOMPtr nextNodeAsCharData = michael@0: do_QueryInterface(nextNode); michael@0: if (nextNodeAsCharData) { michael@0: uint32_t length = nextNode->Length(); michael@0: // Bail out for empty chardata XXX: Do we want to do something else? michael@0: NS_ENSURE_STATE(length); michael@0: nsRefPtr txn; michael@0: res = CreateTxnForDeleteCharacter(nextNodeAsCharData, 0, eNext, michael@0: getter_AddRefs(txn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aOffset = txn->GetOffset(); michael@0: *aLength = txn->GetNumCharsToDelete(); michael@0: aTxn->AppendChild(txn); michael@0: } else { michael@0: // nextNode is not chardata, so tell its parent to delete it michael@0: nsRefPtr txn; michael@0: res = CreateTxnForDeleteNode(nextNode, getter_AddRefs(txn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: aTxn->AppendChild(txn); michael@0: } michael@0: michael@0: NS_ADDREF(*aNode = nextNode); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (nodeAsCharData) { michael@0: // we have chardata, so delete a char at the proper offset michael@0: nsRefPtr txn; michael@0: res = CreateTxnForDeleteCharacter(nodeAsCharData, offset, aAction, michael@0: getter_AddRefs(txn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: aTxn->AppendChild(txn); michael@0: NS_ADDREF(*aNode = node); michael@0: *aOffset = txn->GetOffset(); michael@0: *aLength = txn->GetNumCharsToDelete(); michael@0: } else { michael@0: // we're either deleting a node or chardata, need to dig into the next/prev michael@0: // node to find out michael@0: nsCOMPtr selectedNode; michael@0: if (aAction == ePrevious) { michael@0: selectedNode = GetPriorNode(node, offset, true); michael@0: } else if (aAction == eNext) { michael@0: selectedNode = GetNextNode(node, offset, true); michael@0: } michael@0: michael@0: while (selectedNode && michael@0: selectedNode->IsNodeOfType(nsINode::eDATA_NODE) && michael@0: !selectedNode->Length()) { michael@0: // Can't delete an empty chardata node (bug 762183) michael@0: if (aAction == ePrevious) { michael@0: selectedNode = GetPriorNode(selectedNode, true); michael@0: } else if (aAction == eNext) { michael@0: selectedNode = GetNextNode(selectedNode, true); michael@0: } michael@0: } michael@0: NS_ENSURE_STATE(selectedNode); michael@0: michael@0: nsCOMPtr selectedNodeAsCharData = michael@0: do_QueryInterface(selectedNode); michael@0: if (selectedNodeAsCharData) { michael@0: // we are deleting from a chardata node, so do a character deletion michael@0: uint32_t position = 0; michael@0: if (aAction == ePrevious) { michael@0: position = selectedNode->Length(); michael@0: } michael@0: nsRefPtr delTextTxn; michael@0: res = CreateTxnForDeleteCharacter(selectedNodeAsCharData, position, michael@0: aAction, getter_AddRefs(delTextTxn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(delTextTxn, NS_ERROR_NULL_POINTER); michael@0: michael@0: aTxn->AppendChild(delTextTxn); michael@0: *aOffset = delTextTxn->GetOffset(); michael@0: *aLength = delTextTxn->GetNumCharsToDelete(); michael@0: } else { michael@0: nsRefPtr delElementTxn; michael@0: res = CreateTxnForDeleteNode(selectedNode, getter_AddRefs(delElementTxn)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(delElementTxn, NS_ERROR_NULL_POINTER); michael@0: michael@0: aTxn->AppendChild(delElementTxn); michael@0: } michael@0: michael@0: NS_ADDREF(*aNode = selectedNode); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::CreateRange(nsIDOMNode *aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode *aEndParent, int32_t aEndOffset, michael@0: nsIDOMRange **aRange) michael@0: { michael@0: return nsRange::CreateRange(aStartParent, aStartOffset, aEndParent, michael@0: aEndOffset, aRange); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::AppendNodeToSelectionAsRange(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if(!selection) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr parentNode; michael@0: res = aNode->GetParentNode(getter_AddRefs(parentNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: int32_t offset = GetChildOffset(aNode, parentNode); michael@0: michael@0: nsCOMPtr range; michael@0: res = CreateRange(parentNode, offset, parentNode, offset+1, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); michael@0: michael@0: return selection->AddRange(range); michael@0: } michael@0: michael@0: nsresult nsEditor::ClearSelection() michael@0: { michael@0: nsCOMPtr selection; michael@0: nsresult res = nsEditor::GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: return selection->RemoveAllRanges(); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::CreateHTMLContent(const nsAString& aTag, dom::Element** aContent) michael@0: { michael@0: nsCOMPtr doc = GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: // XXX Wallpaper over editor bug (editor tries to create elements with an michael@0: // empty nodename). michael@0: if (aTag.IsEmpty()) { michael@0: NS_ERROR("Don't pass an empty tag to nsEditor::CreateHTMLContent, " michael@0: "check caller."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return doc->CreateElem(aTag, nullptr, kNameSpaceID_XHTML, michael@0: reinterpret_cast(aContent)); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, michael@0: const nsAString & aAttribute, michael@0: const nsAString & aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: return SetAttribute(aElement, aAttribute, aValue); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement, michael@0: const nsAString & aAttribute, michael@0: bool aSuppressTransaction) michael@0: { michael@0: return RemoveAttribute(aElement, aAttribute); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: // NOTE: When you change this method, you should also change: michael@0: // * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html michael@0: // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html michael@0: // michael@0: // And also when you add new key handling, you need to change the subclass's michael@0: // HandleKeyPressEvent()'s switch statement. michael@0: michael@0: WidgetKeyboardEvent* nativeKeyEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); michael@0: NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, michael@0: "HandleKeyPressEvent gets non-keypress event"); michael@0: michael@0: // if we are readonly or disabled, then do nothing. michael@0: if (IsReadonly() || IsDisabled()) { michael@0: // consume backspace for disabled and readonly textfields, to prevent michael@0: // back in history, which could be confusing to users michael@0: if (nativeKeyEvent->keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { michael@0: aKeyEvent->PreventDefault(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (nativeKeyEvent->keyCode) { michael@0: case nsIDOMKeyEvent::DOM_VK_META: michael@0: case nsIDOMKeyEvent::DOM_VK_WIN: michael@0: case nsIDOMKeyEvent::DOM_VK_SHIFT: michael@0: case nsIDOMKeyEvent::DOM_VK_CONTROL: michael@0: case nsIDOMKeyEvent::DOM_VK_ALT: michael@0: aKeyEvent->PreventDefault(); // consumed michael@0: return NS_OK; michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || michael@0: nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip); michael@0: aKeyEvent->PreventDefault(); // consumed michael@0: return NS_OK; michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE: michael@0: // on certain platforms (such as windows) the shift key michael@0: // modifies what delete does (cmd_cut in this case). michael@0: // bailing here to allow the keybindings to do the cut. michael@0: if (nativeKeyEvent->IsShift() || nativeKeyEvent->IsControl() || michael@0: nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || michael@0: nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip); michael@0: aKeyEvent->PreventDefault(); // consumed michael@0: return NS_OK; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::HandleInlineSpellCheck(EditAction action, michael@0: nsISelection *aSelection, michael@0: nsIDOMNode *previousSelectedNode, michael@0: int32_t previousSelectedOffset, michael@0: nsIDOMNode *aStartNode, michael@0: int32_t aStartOffset, michael@0: nsIDOMNode *aEndNode, michael@0: int32_t aEndOffset) michael@0: { michael@0: // Have to cast action here because this method is from an IDL michael@0: return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange( michael@0: (int32_t)action, aSelection, michael@0: previousSelectedNode, previousSelectedOffset, michael@0: aStartNode, aStartOffset, aEndNode, michael@0: aEndOffset) michael@0: : NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsEditor::FindSelectionRoot(nsINode *aNode) michael@0: { michael@0: nsCOMPtr rootContent = GetRoot(); michael@0: return rootContent.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: nsEditor::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget) michael@0: { michael@0: nsCOMPtr targetNode = do_QueryInterface(aFocusEventTarget); michael@0: NS_ENSURE_TRUE(targetNode, NS_ERROR_INVALID_ARG); michael@0: nsCOMPtr selectionRootContent = FindSelectionRoot(targetNode); michael@0: if (!selectionRootContent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isTargetDoc = michael@0: targetNode->NodeType() == nsIDOMNode::DOCUMENT_NODE && michael@0: targetNode->HasFlag(NODE_IS_EDITABLE); michael@0: michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr selCon; michael@0: rv = GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr selectionPrivate = michael@0: do_QueryInterface(selection); michael@0: NS_ENSURE_TRUE(selectionPrivate, NS_ERROR_UNEXPECTED); michael@0: michael@0: // Init the caret michael@0: nsRefPtr caret = presShell->GetCaret(); michael@0: NS_ENSURE_TRUE(caret, NS_ERROR_UNEXPECTED); michael@0: caret->SetIgnoreUserModify(false); michael@0: caret->SetCaretDOMSelection(selection); michael@0: selCon->SetCaretReadOnly(IsReadonly()); michael@0: selCon->SetCaretEnabled(true); michael@0: michael@0: // Init selection michael@0: selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); michael@0: selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL); michael@0: selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); michael@0: // If the computed selection root isn't root content, we should set it michael@0: // as selection ancestor limit. However, if that is root element, it means michael@0: // there is not limitation of the selection, then, we must set nullptr. michael@0: // NOTE: If we set a root element to the ancestor limit, some selection michael@0: // methods don't work fine. michael@0: if (selectionRootContent->GetParent()) { michael@0: selectionPrivate->SetAncestorLimiter(selectionRootContent); michael@0: } else { michael@0: selectionPrivate->SetAncestorLimiter(nullptr); michael@0: } michael@0: michael@0: // XXX What case needs this? michael@0: if (isTargetDoc) { michael@0: int32_t rangeCount; michael@0: selection->GetRangeCount(&rangeCount); michael@0: if (rangeCount == 0) { michael@0: BeginningOfDocument(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsEditor::FinalizeSelection() michael@0: { michael@0: nsCOMPtr selCon; michael@0: nsresult rv = GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsCOMPtr selection; michael@0: rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsCOMPtr selectionPrivate = do_QueryInterface(selection); michael@0: NS_ENSURE_TRUE_VOID(selectionPrivate); michael@0: michael@0: selectionPrivate->SetAncestorLimiter(nullptr); michael@0: michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: NS_ENSURE_TRUE_VOID(presShell); michael@0: michael@0: selCon->SetCaretEnabled(false); michael@0: michael@0: nsFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: NS_ENSURE_TRUE_VOID(fm); michael@0: fm->UpdateCaretForCaretBrowsingMode(); michael@0: michael@0: if (!HasIndependentSelection()) { michael@0: // If this editor doesn't have an independent selection, i.e., it must michael@0: // mean that it is an HTML editor, the selection controller is shared with michael@0: // presShell. So, even this editor loses focus, other part of the document michael@0: // may still have focus. michael@0: nsCOMPtr doc = GetDocument(); michael@0: ErrorResult ret; michael@0: if (!doc || !doc->HasFocus(ret)) { michael@0: // If the document already lost focus, mark the selection as disabled. michael@0: selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED); michael@0: } else { michael@0: // Otherwise, mark selection as normal because outside of a michael@0: // contenteditable element should be selected with normal selection michael@0: // color after here. michael@0: selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); michael@0: } michael@0: } else if (IsFormWidget() || IsPasswordEditor() || michael@0: IsReadonly() || IsDisabled() || IsInputFiltered()) { michael@0: // In or